diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index 940b095fe29c511aeb5409ba2d6e9610c508d7b7..29217462958ca496490975d758049d9edf745bf1 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -95,6 +95,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForTimelines:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.config/example.yml b/.config/example.yml
index 03864a32994f04662d588951856c783f2ec2848d..0e4f2f5a15cdb5ec41da75382c7e0d2d985b48ca 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -105,6 +105,16 @@ redis:
 #  # You can specify more ioredis options...
 #  #username: example-username
 
+#redisForTimelines:
+#  host: localhost
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+#  # You can specify more ioredis options...
+#  #username: example-username
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 861b0008a047dff9cff035a688b0917a3855595e..a78d91900bedceecf9df984faa3a40a349a86b93 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -4,7 +4,9 @@
 	"service": "app",
 	"workspaceFolder": "/workspace",
 	"features": {
-		"ghcr.io/devcontainers-contrib/features/pnpm:2": {},
+		"ghcr.io/devcontainers-contrib/features/pnpm:2": {
+			"version": "8.8.0"
+		},
 		"ghcr.io/devcontainers/features/node:1": {
 			"version": "20.5.1"
 		}
diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml
index 5dcd41599acd8ba92524a36c6bb5ce0a968ffe2d..3d57d1245daa5141ea62a533505d648d57338c27 100644
--- a/.devcontainer/devcontainer.yml
+++ b/.devcontainer/devcontainer.yml
@@ -95,6 +95,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForTimelines:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee2f11b1564025f64d319a88c5ebb5843e577bc5..d8714599f251b0902fd6055303ca3f7f497115ee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,53 @@
 
 -->
 
+## 2023.10.0
+### NOTE
+- 2023.9.2で導入されたノート編集機能はクオリティの高い実装が困難であることが判明したため撤回されました
+- アップデートを行うと、タイムラインが一時的にリセットされます
+
+### Changes
+- API: users/notes, notes/local-timeline で fileType 指定はできなくなりました
+- API: notes/featured でページネーションは他APIと同様 untilId を使って行うようになりました
+
+### General
+- Feat: ユーザーごとに他ユーザーへの返信をタイムラインに含めるか設定可能になりました
+- Feat: ユーザーリスト内のメンバーごとに他ユーザーへの返信をユーザーリストタイムラインに含めるか設定可能になりました
+- Feat: ユーザーごとのハイライト
+- Feat: プライバシーポリシー・運営者情報(Impressum)の指定が可能になりました
+	- プライバシーポリシーはサーバー登録時に同意確認が入ります
+- Feat: タイムラインがリアルタイム更新中に広告を挿入できるようになりました
+	- デフォルトは無効
+	- 頻度はコントロールパネルから設定できます。運営中のサーバーのTLの流速を見て、最適な値を指定してください。
+- Enhance: ソフトワードミュートとハードワードミュートは統合されました
+- Enhance: モデレーションログ機能の強化
+- Enhance: ローカリゼーションの更新
+- Enhance: 依存関係の更新
+- Fix: ダイレクト投稿をリノートできてしまう問題を修正
+- Fix: ユーザーリストTLにチャンネル投稿が含まれる問題を修正
+
+### Client
+- Feat: 「ファイルの詳細」ページを追加
+	- ドライブのファイルの拡大プレビューができるように
+	- ファイルが添付されたノートの一覧が表示できるように
+- Enhance: 二要素認証のバックアップコード一覧をテキストファイルでダウンロード可能に
+- Enhance: 動画再生時のデフォルトボリュームを30%に
+- Fix: リアクションしたユーザ一覧のUIが稀に左上に残ってしまう不具合を修正
+
+### Server
+- Enhance: drive/files/attached-notes がページネーションに対応しました
+- Enhance: タイムライン取得時のパフォーマンスを大幅に向上
+- Enhance: ハイライト取得時のパフォーマンスを大幅に向上
+- Enhance: トレンドハッシュタグ取得時のパフォーマンスを大幅に向上
+- Enhance: WebSocket接続が多い場合のパフォーマンスを向上
+- Enhance: 不要なPostgreSQLのインデックスを削除しパフォーマンスを向上
+- Fix: 連合なしアンケートに投票をするとUpdateがリモートに配信されてしまうのを修正
+- Fix: nodeinfoにおいてCORS用のヘッダーが設定されていないのを修正
+- Fix: 同じ種類のTLのストリーミングを複数接続できない問題を修正
+- Fix: アンテナTLを途中までしかページネーションできなくなることがある問題を修正
+- Fix: 「ファイル付きのみ」のTLでファイル無しの新着ノートが流れる問題を修正
+- Fix: プロセスが終了しない、あるいは非常に時間がかかる問題を修正
+
 ## 2023.9.3
 ### General
 - Enhance: ノートの翻訳機能の利用可否をロールで設定可能に
diff --git a/chart/files/default.yml b/chart/files/default.yml
index 90b574b99f400eb44aed4f4abc9c4347273e2911..87b2f677ebe2ac0622b549a396ebcc1696a729c3 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -116,6 +116,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
+#redisForTimelines:
+#  host: redis
+#  port: 6379
+#  #family: 0  # 0=Both, 4=IPv4, 6=IPv6
+#  #pass: example-pass
+#  #prefix: example-prefix
+#  #db: 1
+
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 55b7cbb88c9babb521b63951deaf34797933b4c1..e835c4aeeeb6b7124fc21fc493eb24b405e6d0f3 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -1184,11 +1184,6 @@ _wordMute:
   muteWords: "الكلمات المحظورة"
   muteWordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\"."
   muteWordsDescription2: "احصر الكلمات المفتاحية بين بين شرطتين مائلتين لاستخدامها كتعابير نمطية"
-  softDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني."
-  hardDescription: "اخف الملاحظات التي تستوف الشروط من الخيط الزمني.بالإضافة إلى أن هذه الملاحظات ستبقى مخفية حتى وإن تغيرت الشروط."
-  soft: "لينة"
-  hard: "قاسية"
-  mutedNotes: "الملاحظات المكتومة"
 _instanceMute:
   instanceMuteDescription: "هذه سيحجب كل ملاحظات الخوادم المحجوبة ومشاركاتها والردود على تلك الملاحظات حتى وإن كانت من خادم غير محجوب."
   instanceMuteDescription2: "مدخلة لكل سطر"
@@ -1248,8 +1243,6 @@ _sfx:
   note: "الملاحظات"
   noteMy: "ملاحظتي"
   notification: "الإشعارات"
-  chat: "المحادثة"
-  chatBg: "المحادثة (الخلفية)"
   antenna: "الهوائيات"
   channel: "إشعارات القنات"
 _ago:
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 64b32d176b2f107cb4330a5a5803ea36f1043834..4baa3d672e7b3ef0309b82ad01d874611020a84a 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -932,11 +932,6 @@ _wordMute:
   muteWords: "নিঃশব্দ করা শব্দগুলি"
   muteWordsDescription: "স্পেস দিয়ে আলাদা করলে AND শর্ত তৈরি হবে এবং আলাদা লাইনে লিখলে OR শর্ত তৈরি হবে।"
   muteWordsDescription2: "রেগুলার এক্সপ্রেশন ব্যবহার করতে স্ল্যাশ দিয়ে কীওয়ার্ডকে ঘিরে রাখুন।"
-  softDescription: "টাইমলাইন থেকে নির্দিষ্ট শর্তানুযায়ী নোট লুকিয়ে রাখে।"
-  hardDescription: "নির্দিষ্ট শর্তানুযায়ী নোটগুলিকে টাইমলাইন থেকে বাদ দেয়। আপনি শর্ত পরিবর্তন করলেও যে নোটগুলি যোগ করা হয়নি সেগুলি বাদ দেওয়া হবে।"
-  soft: "নমনীয়"
-  hard: "কঠোর"
-  mutedNotes: "মিউট করা নোটগুলি"
 _instanceMute:
   instanceMuteDescription: "কনফিগার করা ইন্সট্যান্সের সব নোট এবং রিনোট মিউট করুন, মিউট করা ইন্সট্যান্সের ব্যবহারকারীদের উত্তর সহ।"
   instanceMuteDescription2: "প্রতিটিকে আলাদা লাইনে লিখুন"
@@ -1000,9 +995,6 @@ _theme:
     infoFg: "তথ্যের পাঠ্য"
     infoWarnBg: "ওয়ার্নিং এর পটভূমি"
     infoWarnFg: "ওয়ার্নিং এর পাঠ্য"
-    cwBg: "CW বাটনের পটভূমি"
-    cwFg: "CW বাটনের পাঠ্য"
-    cwHoverBg: "CW বাটনের পটভূমি (হভার)"
     toastBg: "বিজ্ঞপ্তির পটভূমি"
     toastFg: "বিজ্ঞপ্তির পাঠ্য"
     buttonBg: "বাটনের পটভূমি"
@@ -1020,8 +1012,6 @@ _sfx:
   note: "নোটগুলি"
   noteMy: "নোট (আপনার)"
   notification: "বিজ্ঞপ্তি"
-  chat: "চ্যাট"
-  chatBg: "চ্যাট (ব্যাকগ্রাউন্ড)"
   antenna: "অ্যান্টেনাগুলি"
   channel: "চ্যানেলের বিজ্ঞপ্তি"
 _ago:
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index d1fd73b666c93e4cdae9bbfd0f983a6c78fb8d84..915388006fa67ddaf66a76bb418e79b1c05c8fda 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -398,7 +398,6 @@ _theme:
 _sfx:
   note: "Notes"
   notification: "Notificacions"
-  chat: "Xat"
   antenna: "Antenes"
 _2fa:
   renewTOTPCancel: "No, gràcies"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 762f033b13d7d3a43efc5762edf5bd880471e4b8..6bd21de93a9fc692ee3fdc66195d82bf3df7e9cb 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -1559,11 +1559,6 @@ _wordMute:
   muteWords: "Ztlumená slova"
   muteWordsDescription: "Podmínku AND oddělujte mezerami, podmínku OR oddělujte řádkovými zlomy."
   muteWordsDescription2: "Chcete-li použít regulární výrazy, obklopte klíčová slova lomítky."
-  softDescription: "Skrýt poznámky, které splňují nastavené podmínky, z časové osy."
-  hardDescription: "Zabrání přidání poznámek splňujících nastavené podmínky na časovou osu. Kromě toho nebudou tyto poznámky přidány na časovou osu, ani když se podmínky změní."
-  soft: "Měkký"
-  hard: "Tvrdý"
-  mutedNotes: "Ztlumené poznámky"
 _instanceMute:
   instanceMuteDescription: "Tímhle se ztlumí všechny poznámky/poznámky z uvedených instancí, včetně poznámek uživatelů, kteří odpovídají uživateli ze ztlumené instance."
   instanceMuteDescription2: "Oddělte novými řádky"
@@ -1627,9 +1622,6 @@ _theme:
     infoFg: "Text informací"
     infoWarnBg: "Pozadí varování"
     infoWarnFg: "Text varování"
-    cwBg: "Pozadí CW tlačítka"
-    cwFg: "Text CW tlačítka"
-    cwHoverBg: "Pozadí CW tlačítka (Hover)"
     toastBg: "Pozadí oznámení"
     toastFg: "Text oznámení"
     buttonBg: "Pozadí tlačítka"
@@ -1647,8 +1639,6 @@ _sfx:
   note: "Poznámky"
   noteMy: "Moje poznámka"
   notification: "Oznámení"
-  chat: "Zprávy"
-  chatBg: "Chat (Pozadí)"
   antenna: "Antény"
   channel: "Oznámení kanálu"
 _ago:
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index e7d435a2e621f21c367b3dd808e91a534f8bef32..e4725ca7235ea3be292b13aaf11fbe710b58623a 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1126,6 +1126,15 @@ edited: "Bearbeitet"
 notificationRecieveConfig: "Benachrichtigungseinstellungen"
 mutualFollow: "Gegenseitig gefolgt"
 fileAttachedOnly: "Nur Notizen mit Dateien"
+showRepliesToOthersInTimeline: "Antworten in Chronik anzeigen"
+hideRepliesToOthersInTimeline: "Antworten nicht in Chronik anzeigen"
+externalServices: "Externe Dienste"
+impressum: "Impressum"
+impressumUrl: "Impressums-URL"
+impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend."
+privacyPolicy: "Datenschutzerklärung"
+privacyPolicyUrl: "Datenschutzerklärungs-URL"
+tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung"
 _announcement:
   forExistingUsers: "Nur für existierende Nutzer"
   forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
@@ -1456,7 +1465,6 @@ _role:
     gtlAvailable: "Kann auf die globale Chronik zugreifen"
     ltlAvailable: "Kann auf die lokale Chronik zugreifen"
     canPublicNote: "Kann öffentliche Notizen erstellen"
-    canEditNote: "Notizbearbeitung"
     canInvite: "Erstellung von Einladungscodes für diese Instanz"
     inviteLimit: "Maximalanzahl an Einladungen"
     inviteLimitCycle: "Zyklus des Einladungslimits"
@@ -1476,6 +1484,7 @@ _role:
     descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
     canHideAds: "Kann Werbung ausblenden"
     canSearchNotes: "Nutzung der Notizsuchfunktion"
+    canUseTranslator: "Verwendung des Ãœbersetzers"
   _condition:
     isLocal: "Lokaler Benutzer"
     isRemote: "Benutzer fremder Instanz"
@@ -1524,6 +1533,10 @@ _ad:
   reduceFrequencyOfThisAd: "Diese Werbung weniger anzeigen"
   hide: "Ausblenden"
   timezoneinfo: "Der Wochentag wird durch die Serverzeitzone bestimmt."
+  adsSettings: "Werbeeinstellungen"
+  notesPerOneAd: "Werbeintervall während Echtzeitaktualisierung (Notizen pro Werbung)"
+  setZeroToDisable: "Setze dies auf 0, um Werbung während Echtzeitaktualisierung zu deaktivieren"
+  adsTooClose: "Durch den momentan sehr niedrigen Werbeintervall kann es zu einer starken Verschlechterung der Benutzererfahrung kommen."
 _forgotPassword:
   enterEmail: "Gib die Email-Adresse ein, mit der du dich registriert hast. An diese wird ein Link gesendet, mit dem du dein Passwort zurücksetzen kannst."
   ifNoEmail: "Solltest du bei der Registrierung keine Email-Adresse angegeben haben, wende dich bitte an den Administrator."
@@ -1609,11 +1622,6 @@ _wordMute:
   muteWords: "Stummgeschaltete Wörter"
   muteWordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen."
   muteWordsDescription2: "Umgib Schlüsselworter mit Schrägstrichen, um Reguläre Ausdrücke zu verwenden."
-  softDescription: "Notizen, die die angegebenen Konditionen erfüllen, in der Chronik ausblenden."
-  hardDescription: "Verhindern, dass Notizen, die die angegebenen Konditionen erfüllen, der Chronik hinzugefügt werden. Zudem werden diese Notizen auch nicht der Chronik hinzugefügt, falls die Konditionen geändert werden."
-  soft: "Leicht"
-  hard: "Schwer"
-  mutedNotes: "Stummgeschaltete Notizen"
 _instanceMute:
   instanceMuteDescription: "Schaltet alle Notizen/Renotes stumm, die von den gelisteten Instanzen stammen, inklusive Antworten von Benutzern an einen Benutzer einer stummgeschalteten Instanz."
   instanceMuteDescription2: "Instanzen getrennt durch Zeilenumbrüchen angeben"
@@ -1677,9 +1685,6 @@ _theme:
     infoFg: "Text von Informationen"
     infoWarnBg: "Hintergrund von Warnungen"
     infoWarnFg: "Text von Warnungen"
-    cwBg: "Hintergrund des Inhaltswarnungsknopfs"
-    cwFg: "Text des Inhaltswarnungsknopfs"
-    cwHoverBg: "Hintergrund des Inhaltswarnungsknopfs (Mouseover)"
     toastBg: "Hintergrund von Benachrichtigungen"
     toastFg: "Text von Benachrichtigungen"
     buttonBg: "Hintergrund von Schaltflächen"
@@ -1697,8 +1702,6 @@ _sfx:
   note: "Notizen"
   noteMy: "Meine Notizen"
   notification: "Benachrichtigungen"
-  chat: "Chat"
-  chatBg: "Chat (Hintergrund)"
   antenna: "Antennen"
   channel: "Kanalbenachrichtigung"
 _ago:
@@ -2138,3 +2141,11 @@ _moderationLogTypes:
   createAd: "Werbung erstellt"
   deleteAd: "Werbung gelöscht"
   updateAd: "Werbung aktualisiert"
+_fileViewer:
+  title: "Dateiinformationen"
+  type: "Dateityp"
+  size: "Dateigröße"
+  url: "URL"
+  uploadedAt: "Hochgeladen am"
+  attachedNotes: "Zugehörige Notizen"
+  thisPageCanBeSeenFromTheAuthor: "Nur der Benutzer, der diese Datei hochgeladen hat, kann diese Seite sehen."
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index e46efcec1f74bd2035a55f325161645637df4a19..9392fd12feb86a561becb4eff5ac9638a4ad22af 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -303,8 +303,6 @@ _theme:
 _sfx:
   note: "Σημειώματα"
   notification: "Ειδοποιήσεις"
-  chat: "Συνομιλία"
-  chatBg: "Συνομιλία (Παρασκήνιο)"
   antenna: "Αντένες"
   channel: "Ειδοποιήσεις καναλιών"
 _ago:
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 9ed8ad9f238734fdf064f8a8b093e1bde5357d3f..66825eaa7fe108053cb23645a48919586a32a42e 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1126,6 +1126,15 @@ edited: "Edited"
 notificationRecieveConfig: "Notification Settings"
 mutualFollow: "Mutual follow"
 fileAttachedOnly: "Only notes with files"
+showRepliesToOthersInTimeline: "Show replies to others in TL"
+hideRepliesToOthersInTimeline: "Hide replies to others from TL"
+externalServices: "External Services"
+impressum: "Impressum"
+impressumUrl: "Impressum URL"
+impressumDescription: "In some countries, like germany, the inclusion of operator contact information (an Impressum) is legally required for commercial websites."
+privacyPolicy: "Privacy Policy"
+privacyPolicyUrl: "Privacy Policy URL"
+tosAndPrivacyPolicy: "Terms of Service and Privacy Policy"
 _announcement:
   forExistingUsers: "Existing users only"
   forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
@@ -1456,7 +1465,6 @@ _role:
     gtlAvailable: "Can view the global timeline"
     ltlAvailable: "Can view the local timeline"
     canPublicNote: "Can send public notes"
-    canEditNote: "Note editing"
     canInvite: "Can create instance invite codes"
     inviteLimit: "Invite limit"
     inviteLimitCycle: "Invite limit cooldown"
@@ -1476,6 +1484,7 @@ _role:
     descriptionOfRateLimitFactor: "Lower rate limits are less restrictive, higher ones more restrictive. "
     canHideAds: "Can hide ads"
     canSearchNotes: "Usage of note search"
+    canUseTranslator: "Translator usage"
   _condition:
     isLocal: "Local user"
     isRemote: "Remote user"
@@ -1524,6 +1533,10 @@ _ad:
   reduceFrequencyOfThisAd: "Show this ad less"
   hide: "Hide"
   timezoneinfo: "The day of the week is determined from the server's timezone."
+  adsSettings: "Ad settings"
+  notesPerOneAd: "Real-time update ad placement interval (Notes per ad)"
+  setZeroToDisable: "Set this value to 0 to disable real-time update ads"
+  adsTooClose: "The current ad interval may significantly worsen the user experience due to being too low."
 _forgotPassword:
   enterEmail: "Enter the email address you used to register. A link with which you can reset your password will then be sent to it."
   ifNoEmail: "If you did not use an email during registration, please contact the instance administrator instead."
@@ -1609,11 +1622,6 @@ _wordMute:
   muteWords: "Muted words"
   muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
   muteWordsDescription2: "Surround keywords with slashes to use regular expressions."
-  softDescription: "Hide notes that fulfil the set conditions from the timeline."
-  hardDescription: "Prevents notes fulfilling the set conditions from being added to the timeline. In addition, these notes will not be added to the timeline even if the conditions are changed."
-  soft: "Soft"
-  hard: "Hard"
-  mutedNotes: "Muted notes"
 _instanceMute:
   instanceMuteDescription: "This will mute any notes/renotes from the listed instances, including those of users replying to a user from a muted instance."
   instanceMuteDescription2: "Separate with newlines"
@@ -1677,9 +1685,6 @@ _theme:
     infoFg: "Information text"
     infoWarnBg: "Warning background"
     infoWarnFg: "Warning text"
-    cwBg: "CW button background"
-    cwFg: "CW button text"
-    cwHoverBg: "CW button background (Hover)"
     toastBg: "Notification background"
     toastFg: "Notification text"
     buttonBg: "Button background"
@@ -1697,8 +1702,6 @@ _sfx:
   note: "New note"
   noteMy: "Own note"
   notification: "Notifications"
-  chat: "Chat"
-  chatBg: "Chat (Background)"
   antenna: "Antennas"
   channel: "Channel notifications"
 _ago:
@@ -2138,3 +2141,11 @@ _moderationLogTypes:
   createAd: "Ad created"
   deleteAd: "Ad deleted"
   updateAd: "Ad updated"
+_fileViewer:
+  title: "File details"
+  type: "File type"
+  size: "Filesize"
+  url: "URL"
+  uploadedAt: "Uploaded at"
+  attachedNotes: "Attached notes"
+  thisPageCanBeSeenFromTheAuthor: "This page can only be seen by the user who uploaded this file."
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index d663bd829c5103512fc7a4898b673f5114f84034..3a01f40dfdd8546ee7ae041dd3bd7534ba44e481 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -1603,11 +1603,6 @@ _wordMute:
   muteWords: "Palabras que silenciar"
   muteWordsDescription: "Separar con espacios indica una declaracion And, separar con lineas nuevas indica una declaracion Or。"
   muteWordsDescription2: "Encerrar las palabras clave entre numerales para usar expresiones regulares"
-  softDescription: "Ocultar en la linea de tiempo las notas que cumplen las condiciones"
-  hardDescription: "Evitar que se agreguen a la linea de tiempo las notas que cumplen las condiciones. Las notas no agregadas seguirán quitadas aunque cambien las condiciones."
-  soft: "Suave"
-  hard: "Duro"
-  mutedNotes: "Notas silenciadas"
 _instanceMute:
   instanceMuteDescription: "Silencia todas las notas y reposts de la instancias seleccionadas, incluyendo respuestas a los usuarios de las mismas"
   instanceMuteDescription2: "Separar por líneas"
@@ -1671,9 +1666,6 @@ _theme:
     infoFg: "Texto de información"
     infoWarnBg: "Fondo de advertencias"
     infoWarnFg: "Texto de advertencias"
-    cwBg: "Fondo del botón CW"
-    cwFg: "Texto del botón CW"
-    cwHoverBg: "Fondo del botón CW (hover)"
     toastBg: "Fondo de notificaciones"
     toastFg: "Texto de notificaciones"
     buttonBg: "Fondo de botón"
@@ -1691,8 +1683,6 @@ _sfx:
   note: "Notas"
   noteMy: "Nota (a mí mismo)"
   notification: "Notificaciones"
-  chat: "Chat"
-  chatBg: "Chat (Fondo)"
   antenna: "Antena receptora"
   channel: "Notificaciones del canal"
 _ago:
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index db19b66880e6bf44b31f8bd9b0720c1d69ef8282..0ad1247ff0727eed2132f499c4ad5a1ed06c3679 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -45,6 +45,7 @@ pin: "Épingler sur le profil"
 unpin: "Désépingler"
 copyContent: "Copier le contenu"
 copyLink: "Copier le lien"
+copyLinkRenote: "Copier le lien de la renote"
 delete: "Supprimer"
 deleteAndEdit: "Supprimer et réécrire"
 deleteAndEditConfirm: "Êtes-vous sûr de vouloir effacer cette note et la modifier ? Vous perdrez toutes les réactions, renotes et réponses."
@@ -129,6 +130,8 @@ unmarkAsSensitive: "Supprimer le marquage comme sensible"
 enterFileName: "Entrer le nom du fichier"
 mute: "Masquer"
 unmute: "Ne plus masquer"
+renoteMute: "Masquer les renotes"
+renoteUnmute: "Ne plus masquer les renotes"
 block: "Bloquer"
 unblock: "Débloquer"
 suspend: "Suspendre"
@@ -414,6 +417,7 @@ moderator: "Modérateur·rice·s"
 moderation: "Modérations"
 moderationNote: "Note de modération"
 addModerationNote: "Ajouter une note de modération"
+moderationLogs: "Journal de modération"
 nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s"
 securityKeyAndPasskey: "Sécurité et clés de sécurité"
 securityKey: "Clé de sécurité"
@@ -472,6 +476,7 @@ aboutX: "À propos de {x}"
 emojiStyle: "Style des émojis"
 native: "Natif"
 disableDrawer: "Les menus ne s'affichent pas dans le tiroir"
+showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol"
 noHistory: "Pas d'historique"
 signinHistory: "Historique de connexion"
 enableAdvancedMfm: "Activer la MFM avancée"
@@ -647,6 +652,7 @@ behavior: "Comportement"
 sample: "Exemple"
 abuseReports: "Signalements"
 reportAbuse: "Signaler"
+reportAbuseRenote: "Signaler la renote"
 reportAbuseOf: "Signaler {name}"
 fillAbuseReportDescription: "Veuillez expliquer les raisons du signalement. S'il s'agit d'une note précise, veuillez en donner le lien."
 abuseReported: "Le rapport est envoyé. Merci."
@@ -671,6 +677,8 @@ clip: "Clip"
 createNew: "Créer nouveau"
 optional: "Facultatif"
 createNewClip: "Créer un nouveau clip"
+unclip: "Supprimer le clip"
+confirmToUnclipAlreadyClippedNote: "Cette note fait déjà partie du clip « {name} ». Souhaitez-vous la supprimer de ce clip ?"
 public: "Public"
 private: "Privé"
 i18nInfo: "Misskey est traduit dans différentes langues par des bénévoles. Vous pouvez contribuer à {link}."
@@ -933,12 +941,15 @@ unsubscribePushNotification: "Désactiver les notifications push"
 pushNotificationAlreadySubscribed: "Les notifications push sont déjà activées"
 pushNotificationNotSupported: "Votre navigateur ou votre instance ne prend pas en charge les notifications push"
 sendPushNotificationReadMessage: "Supprimer les notifications push une fois que les notifications ou messages pertinents ont été lus."
+windowMaximize: "Maximiser"
+windowMinimize: "Minimaliser"
 windowRestore: "Restaurer"
 caption: "Libellé"
 loggedInAsBot: "Connecté actuellement en tant que bot"
 tools: "Outils"
 cannotLoad: "Chargement impossible"
 like: "J'aime"
+unlike: "Ne plus aimer"
 numberOfLikes: "Favoris"
 show: "Affichage"
 neverShow: "Ne plus afficher"
@@ -949,6 +960,7 @@ noRole: "Aucun rôle"
 normalUser: "Simple utilisateur·rice"
 undefined: "Non défini"
 assign: "Attribuer"
+unassign: "Retirer"
 color: "Couleur"
 manageCustomEmojis: "Gestion des émojis personnalisés"
 preset: "Préréglage"
@@ -958,12 +970,16 @@ thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes."
 thisPostMayBeAnnoyingHome: "Publier vers le fil principal"
 thisPostMayBeAnnoyingCancel: "Annuler"
 thisPostMayBeAnnoyingIgnore: "Publier quand-même"
+collapseRenotes: "Réduire les renotes déjà vues"
 internalServerError: "Erreur interne du serveur"
 copyErrorInfo: "Copier les détails de l’erreur"
 exploreOtherServers: "Trouver une autre instance"
 disableFederationOk: "Désactiver"
 likeOnly: "Les favoris uniquement"
+sensitiveWords: "Mots sensibles"
+notesSearchNotAvailable: "La recherche de notes n'est pas disponible."
 license: "Licence"
+myClips: "Mes clips"
 video: "Vidéo"
 videos: "Vidéos"
 dataSaver: "Économiseur de données"
@@ -973,6 +989,7 @@ accountMovedShort: "Ce compte a migré"
 operationForbidden: "Opération non autorisée"
 addMemo: "Ajouter un mémo"
 reactionsList: "Réactions"
+renotesList: "Liste de renotes"
 notificationDisplay: "Style des notifications"
 leftTop: "En haut à gauche"
 rightTop: "En haut à droite"
@@ -982,6 +999,7 @@ vertical: "Vertical"
 horizontal: "Latéral"
 serverRules: "Règles du serveur"
 archive: "Archive"
+displayOfNote: "Affichage de la note"
 youFollowing: "Abonné·e"
 options: "Options"
 later: "Plus tard"
@@ -1001,6 +1019,7 @@ pinnedList: "Liste épinglée"
 notifyNotes: "Notifier à propos des nouvelles notes"
 authentication: "Authentification"
 authenticationRequiredToContinue: "Veuillez vous authentifier pour continuer"
+showRenotes: "Afficher les renotes"
 _announcement:
   readConfirmTitle: "Marquer comme lu ?"
 _initialAccountSetting:
@@ -1082,12 +1101,20 @@ _achievements:
       title: "Beaucoup d'amis"
     _followers10:
       title: "Abonnez-moi !"
+      description: "Obtenir plus de 10 abonné·e·s"
+    _followers50:
+      description: "Obtenir plus de 50 abonné·e·s"
     _followers100:
       title: "Populaire"
+      description: "Obtenir plus de 100 abonné·e·s"
+    _followers300:
+      description: "Obtenir plus de 300 abonné·e·s"
     _followers500:
       title: "Tour radio"
+      description: "Obtenir plus de 500 abonné·e·s"
     _followers1000:
       title: "Influenceur·euse"
+      description: "Obtenir plus de 1000 abonné·e·s"
     _iLoveMisskey:
       title: "J’adore Misskey"
       description: "Publication « J’❤ #Misskey »"
@@ -1151,6 +1178,7 @@ _role:
     high: "Haute"
   _options:
     canManageCustomEmojis: "Gestion des émojis personnalisés"
+    wordMuteMax: "Nombre maximal de caractères dans le filtre de mots"
 _sensitiveMediaDetection:
   description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement."
   sensitivity: "Sensibilité de la détection"
@@ -1267,11 +1295,6 @@ _wordMute:
   muteWords: "Mots à filtrer"
   muteWordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
   muteWordsDescription2: "Pour utiliser des expressions régulières (regex), mettez les mots-clés entre barres obliques."
-  softDescription: "Masquez les notes de votre fil selon les paramètres que vous définissez."
-  hardDescription: "Empêchez votre fil de charger les notes selon les paramètres que vous définissez. Cette action est irréversible : si vous modifiez ces paramètres plus tard, les notes précédemment filtrées ne seront pas récupérées."
-  soft: "Doux"
-  hard: "Strict"
-  mutedNotes: "Notes filtrées"
 _instanceMute:
   instanceMuteDescription: "Met en sourdine toutes les notes et renotes de l'instance configurée, y compris les réponses aux utilisateurs de l'instance muette."
   instanceMuteDescription2: "Séparer avec de nouvelles lignes"
@@ -1335,9 +1358,6 @@ _theme:
     infoFg: "Texte d'information"
     infoWarnBg: "Arrière-plan des avertissements"
     infoWarnFg: "Texte d’avertissement"
-    cwBg: "Arrière-plan du CW"
-    cwFg: "Texte du bouton CW"
-    cwHoverBg: "Arrière-plan du bouton CW (survolé)"
     toastBg: "Arrière-plan de la bulle de notification"
     toastFg: "Texte de la bulle de notification"
     buttonBg: "Arrière-plan du bouton"
@@ -1355,8 +1375,6 @@ _sfx:
   note: "Nouvelle note"
   noteMy: "Ma note"
   notification: "Notifications"
-  chat: "Discuter"
-  chatBg: "Discussion (arrière-plan)"
   antenna: "Réception de l’antenne"
   channel: "Notifications de canal"
 _ago:
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 0e067c5383e5ceca1bfe946af0843dee0458d26d..90bca65119a5d23e7eb3fd186810122ff8309e7f 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -1564,11 +1564,6 @@ _wordMute:
   muteWords: "Kata yang dibisukan"
   muteWordsDescription: "Pisahkan dengan spasi untuk kondisi AND. Pisahkan dengan baris baru untuk kondisi OR."
   muteWordsDescription2: "Kurung kata kunci dengan garis miring untuk menggunakan ekspresi reguler."
-  softDescription: "Sembunyikan catatan yang memenuhi aturan kondisi dari lini masa."
-  hardDescription: "Cegah catatan memenuhi aturan kondisi dari ditambahkan ke lini masa. Dengan tambahan, catatan berikut tidak akan ditambahkan ke lini masa meskipun jika kondisi tersebut diubah."
-  soft: "Lembut"
-  hard: "Keras"
-  mutedNotes: "Catatan yang dibisukan"
 _instanceMute:
   instanceMuteDescription: "Pengaturan ini akan membisukan note/renote apa saja dari instansi yang terdaftar, termasuk pengguna yang membalas pengguna lain dalam instansi yang dibisukan."
   instanceMuteDescription2: "Pisah dengan baris baru"
@@ -1632,9 +1627,6 @@ _theme:
     infoFg: "Teks informasi"
     infoWarnBg: "Latar belakang peringatan"
     infoWarnFg: "Teks peringatan"
-    cwBg: "Latar belakang tombol Sembunyikan Konten"
-    cwFg: "Teks tombol Sembunyikan Konten"
-    cwHoverBg: "Latar belakang tombol Sembunyikan Konten (Mengambang)"
     toastBg: "Latar belakang notifikasi"
     toastFg: "Teks notifikasi"
     buttonBg: "Latar belakang tombol"
@@ -1652,8 +1644,6 @@ _sfx:
   note: "Catatan"
   noteMy: "Catatan (Saya)"
   notification: "Notifikasi"
-  chat: "Pesan"
-  chatBg: "Obrolan (Latar Belakang)"
   antenna: "Penerimaan Antenna"
   channel: "Notifikasi Kanal"
 _ago:
diff --git a/locales/index.d.ts b/locales/index.d.ts
index a9de0ad9652f8b466dbcf7364aa94fe142a138b8..2494c1709b3f0694864531926a8ddb6c0f126d34 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1129,6 +1129,15 @@ export interface Locale {
     "notificationRecieveConfig": string;
     "mutualFollow": string;
     "fileAttachedOnly": string;
+    "showRepliesToOthersInTimeline": string;
+    "hideRepliesToOthersInTimeline": string;
+    "externalServices": string;
+    "impressum": string;
+    "impressumUrl": string;
+    "impressumDescription": string;
+    "privacyPolicy": string;
+    "privacyPolicyUrl": string;
+    "tosAndPrivacyPolicy": string;
     "_announcement": {
         "forExistingUsers": string;
         "forExistingUsersDescription": string;
@@ -1542,7 +1551,6 @@ export interface Locale {
             "gtlAvailable": string;
             "ltlAvailable": string;
             "canPublicNote": string;
-            "canEditNote": string;
             "canInvite": string;
             "inviteLimit": string;
             "inviteLimitCycle": string;
@@ -1619,6 +1627,10 @@ export interface Locale {
         "reduceFrequencyOfThisAd": string;
         "hide": string;
         "timezoneinfo": string;
+        "adsSettings": string;
+        "notesPerOneAd": string;
+        "setZeroToDisable": string;
+        "adsTooClose": string;
     };
     "_forgotPassword": {
         "enterEmail": string;
@@ -1719,11 +1731,6 @@ export interface Locale {
         "muteWords": string;
         "muteWordsDescription": string;
         "muteWordsDescription2": string;
-        "softDescription": string;
-        "hardDescription": string;
-        "soft": string;
-        "hard": string;
-        "mutedNotes": string;
     };
     "_instanceMute": {
         "instanceMuteDescription": string;
@@ -1789,9 +1796,6 @@ export interface Locale {
             "infoFg": string;
             "infoWarnBg": string;
             "infoWarnFg": string;
-            "cwBg": string;
-            "cwFg": string;
-            "cwHoverBg": string;
             "toastBg": string;
             "toastFg": string;
             "buttonBg": string;
@@ -1811,8 +1815,6 @@ export interface Locale {
         "note": string;
         "noteMy": string;
         "notification": string;
-        "chat": string;
-        "chatBg": string;
         "antenna": string;
         "channel": string;
     };
@@ -2289,6 +2291,15 @@ export interface Locale {
         "deleteAd": string;
         "updateAd": string;
     };
+    "_fileViewer": {
+        "title": string;
+        "type": string;
+        "size": string;
+        "url": string;
+        "uploadedAt": string;
+        "attachedNotes": string;
+        "thisPageCanBeSeenFromTheAuthor": string;
+    };
 }
 declare const locales: {
     [lang: string]: Locale;
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 83b63e15d21cf03fab9c86c4b54de0be9f01d1dd..fa8670d115a0d14eec116d50b25c6a2bfa36a0dc 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -64,7 +64,7 @@ reply: "Rispondi"
 loadMore: "Mostra di più"
 showMore: "Espandi"
 showLess: "Comprimi"
-youGotNewFollower: "Ti sta seguendo"
+youGotNewFollower: "Adesso ti segue"
 receiveFollowRequest: "Hai ricevuto una richiesta di follow"
 followRequestAccepted: "Ha accettato la tua richiesta di follow"
 mention: "Menzioni"
@@ -78,7 +78,7 @@ download: "Scarica"
 driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?"
 unfollowConfirm: "Vuoi davvero smettere di seguire {name}?"
 exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive."
-importRequested: "Hai richiesto un'importazione. Può volerci tempo. "
+importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo."
 lists: "Liste"
 noLists: "Nessuna lista"
 note: "Nota"
@@ -113,7 +113,7 @@ cantReRenote: "È impossibile rinotare una Rinota."
 quote: "Cita"
 inChannelRenote: "Rinota nel canale"
 inChannelQuote: "Cita nel canale"
-pinnedNote: "Nota fissata"
+pinnedNote: "Nota in primo piano"
 pinned: "Fissa sul profilo"
 you: "Tu"
 clickToShow: "Clicca per visualizzare"
@@ -186,7 +186,7 @@ recipient: "Destinatario"
 annotation: "Annotazione preventiva"
 federation: "Federazione"
 instances: "Istanza"
-registeredAt: "Registrato presso"
+registeredAt: "Prima federazione"
 latestRequestReceivedAt: "Ultima richiesta ricevuta"
 latestStatus: "Ultimo stato"
 storageUsage: "Capienza dei dischi"
@@ -336,7 +336,7 @@ instanceName: "Nome dell'istanza"
 instanceDescription: "Descrizione dell'istanza"
 maintainerName: "Nome dell'amministratore"
 maintainerEmail: "Indirizzo e-mail dell'amministratore"
-tosUrl: "URL dei termini del servizio e della privacy"
+tosUrl: "URL delle condizioni d'uso"
 thisYear: "Anno"
 thisMonth: "Mese"
 today: "Oggi"
@@ -364,7 +364,7 @@ pinnedUsersDescription: "Elenca gli/le utenti che vuoi fissare in cima alla pagi
 pinnedPages: "Pagine in evidenza"
 pinnedPagesDescription: "Specifica il percorso delle pagine che vuoi fissare in cima alla pagina dell'istanza. Una pagina per riga."
 pinnedClipId: "ID della Clip in evidenza"
-pinnedNotes: "Nota fissata"
+pinnedNotes: "Note in primo piano"
 hcaptcha: "hCaptcha"
 enableHcaptcha: "Abilita hCaptcha"
 hcaptchaSiteKey: "Chiave del sito"
@@ -384,7 +384,7 @@ name: "Nome"
 antennaSource: "Fonte dell'antenna"
 antennaKeywords: "Parole chiavi da ricevere"
 antennaExcludeKeywords: "Parole chiavi da escludere"
-antennaKeywordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con un'interruzzione riga indica la condizione \"O\"."
+antennaKeywordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
 notifyAntenna: "Invia notifiche delle nuove note"
 withFileAntenna: "Solo note con file in allegato"
 enableServiceworker: "Abilita ServiceWorker"
@@ -393,7 +393,7 @@ caseSensitive: "Sensibile alla distinzione tra maiuscole e minuscole"
 withReplies: "Includere le risposte"
 connectedTo: "Connessione ai seguenti profili:"
 notesAndReplies: "Note e risposte"
-withFiles: "Con file in allegato"
+withFiles: "Con allegati"
 silence: "Silenzia"
 silenceConfirm: "Vuoi davvero silenziare questo profilo?"
 unsilence: "Riattiva"
@@ -461,7 +461,7 @@ invitationCode: "Codice di invito"
 checking: "Confermando"
 available: "Disponibile"
 unavailable: "Il nome utente è già in uso"
-usernameInvalidFormat: "Il nome utente può contenere solo lettere, numeri e '_'"
+usernameInvalidFormat: "Il nome utente deve avere solo caratteri alfanumerici e trattino basso '_'"
 tooShort: "Troppo breve"
 tooLong: "Troppo lungo"
 weakPassword: "Password debole"
@@ -1121,7 +1121,20 @@ unnotifyNotes: "Interrompi le notifiche di nuove Note"
 authentication: "Autenticazione"
 authenticationRequiredToContinue: "Per procedere, è richiesta l'autenticazione"
 dateAndTime: "Data e Ora"
-showRenotes: "Leggi le Rinota"
+showRenotes: "Includi le Rinota"
+edited: "Modificato"
+notificationRecieveConfig: "Preferenze di notifica"
+mutualFollow: "Follow reciproco"
+fileAttachedOnly: "Solo con allegati"
+showRepliesToOthersInTimeline: "Risposte altrui nella TL"
+hideRepliesToOthersInTimeline: "Nascondi Riposte altrui nella TL"
+externalServices: "Servizi esterni"
+impressum: "Dichiarazione di proprietà"
+impressumUrl: "URL della dichiarazione di proprietà"
+impressumDescription: "La dichiarazione di proprietà, è obbligatoria in alcuni paesi come la Germania (Impressum)."
+privacyPolicy: "Informativa sulla privacy"
+privacyPolicyUrl: "URL della informativa privacy"
+tosAndPrivacyPolicy: "Condizioni d'uso e informativa sulla privacy"
 _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."
@@ -1451,14 +1464,14 @@ _role:
   _options:
     gtlAvailable: "Disponibilità della Timeline Federata"
     ltlAvailable: "Disponibilità della Timeline Locale"
-    canPublicNote: "Può scrivere Note con Visibilità Pubblica"
-    canInvite: "Genera codici di invito all'istanza"
+    canPublicNote: "Scrivere Note con Visibilità Pubblica"
+    canInvite: "Generare codici di invito all'istanza"
     inviteLimit: "Limite di codici invito"
     inviteLimitCycle: "Intervallo di emissione del codice di invito"
     inviteExpirationTime: "Scadenza del codice di invito"
     canManageCustomEmojis: "Gestire le emoji personalizzate"
     driveCapacity: "Capienza del Drive"
-    alwaysMarkNsfw: "Imposta sempre come NSFW"
+    alwaysMarkNsfw: "Impostare sempre come esplicito (NSFW)"
     pinMax: "Quantità massima di Note in primo piano"
     antennaMax: "Quantità massima di Antenne"
     wordMuteMax: "Lunghezza massima del filtro parole"
@@ -1469,8 +1482,9 @@ _role:
     userEachUserListsMax: "Quantità massima di profili per lista"
     rateLimitFactor: "Limite del rapporto"
     descriptionOfRateLimitFactor: "I rapporti più bassi sono meno restrittivi, quelli più alti lo sono di più."
-    canHideAds: "Può nascondere i banner"
+    canHideAds: "Nascondere i banner"
     canSearchNotes: "Ricercare nelle Note"
+    canUseTranslator: "Tradurre le Note"
   _condition:
     isLocal: "Profilo locale"
     isRemote: "Profilo remoto"
@@ -1519,6 +1533,10 @@ _ad:
   reduceFrequencyOfThisAd: "Visualizza questa pubblicità meno spesso"
   hide: "Nascondi"
   timezoneinfo: "Il giorno della settimana è determinato in base al fuso orario del server."
+  adsSettings: "Impostazioni banner"
+  notesPerOneAd: "Quantità di Note tra i banner"
+  setZeroToDisable: "Imposta 0 (zero) per disattivare la distribuzione dei banner durante gli aggiornamenti in tempo reale"
+  adsTooClose: "Attenzione, l'intervallo di pubblicazione dei banner è molto breve, potrebbe infastidire significativamente la fruizione"
 _forgotPassword:
   enterEmail: "Inserisci l'indirizzo di posta elettronica che hai registrato nel tuo profilo. Il collegamento necessario per ripristinare la password verrà inviato a questo indirizzo."
   ifNoEmail: "Se il tuo indirizzo email non risulta registrato, contatta l'amministrazione dell'istanza."
@@ -1530,7 +1548,7 @@ _gallery:
   unlike: "Non mi piace più"
 _email:
   _follow:
-    title: "Ha iniziato a seguirti"
+    title: "Adesso ti segue"
   _receiveFollowRequest:
     title: "Hai ricevuto una richiesta di follow"
 _plugin:
@@ -1602,13 +1620,8 @@ _menuDisplay:
   hide: "Nascondere"
 _wordMute:
   muteWords: "Parole da filtrare"
-  muteWordsDescription: "Separare con uno spazio indica la condizione \"E\". Separare con una interruzione di riga, indica la condizione \"O\""
+  muteWordsDescription: "Sparando con uno spazio indichi la condizione E (and). Separando con un a capo, indichi la condizione O (or)."
   muteWordsDescription2: "Se vuoi indicare delle Espressioni Regolari (regexp), metti la condizione all'interno di due slash (/)"
-  softDescription: "Verranno nascoste da tutte le Timeline quelle Note che soddisfano le seguenti condizioni"
-  hardDescription: "Impedisci alla istanza di caricare Note che soddisfano le seguenti condizioni. Le Note già filtrate sono già scomparse in modo irreversibile, fino al cambiamento delle condizioni. Dopo di che scompariranno quelle che soddisfano le nuove condizioni."
-  soft: "Leggero"
-  hard: "Pesante"
-  mutedNotes: "Note filtrate"
 _instanceMute:
   instanceMuteDescription: "Disattiva tutte le note, le note di rinvio (condivisione) dell'istanza configurata, comprese le risposte agli utenti dell'istanza."
   instanceMuteDescription2: "Impostazione separata da una nuova riga"
@@ -1617,7 +1630,7 @@ _instanceMute:
 _theme:
   explore: "Esplora temi"
   install: "Installa un tema"
-  manage: "Gerisci temi"
+  manage: "Gestione temi"
   code: "Codice tema"
   description: "Descrizione"
   installed: "{name} è installato"
@@ -1672,9 +1685,6 @@ _theme:
     infoFg: "Testo di informazioni"
     infoWarnBg: "Sfondo degli avvisi"
     infoWarnFg: "Testo di avviso"
-    cwBg: "Sfondo del CW"
-    cwFg: "Testo del pulsante CW"
-    cwHoverBg: "Sfondo del pulsante CW (sorvolato)"
     toastBg: "Sfondo di notifica a comparsa"
     toastFg: "Testo di notifica a comparsa"
     buttonBg: "Sfondo del pulsante"
@@ -1692,8 +1702,6 @@ _sfx:
   note: "Nota"
   noteMy: "Mia nota"
   notification: "Notifiche"
-  chat: "Messaggi"
-  chatBg: "Chat (sfondo)"
   antenna: "Ricezione dell'antenna"
   channel: "Notifiche di canale"
 _ago:
@@ -1878,7 +1886,7 @@ _visibility:
   followersDescription: "Visibile solo ai tuoi follower"
   specified: "Nota diretta"
   specifiedDescription: "Visibile solo ai profili menzionati"
-  disableFederation: "Non federare"
+  disableFederation: "Senza federazione"
   disableFederationDescription: "Non spedire attività alle altre istanze remote"
 _postForm:
   replyPlaceholder: "Rispondi a questa nota..."
@@ -2018,7 +2026,7 @@ _notification:
   youGotReply: "{name} ti ha risposto"
   youGotQuote: "{name} ha citato la tua Nota e ha detto"
   youRenoted: "{name} ha rinotato"
-  youWereFollowed: "Ha iniziato a seguirti"
+  youWereFollowed: "Adesso ti segue"
   youReceivedFollowRequest: "Hai ricevuto una richiesta di follow"
   yourFollowRequestAccepted: "La tua richiesta di follow è stata accettata"
   pollEnded: "Risultati del sondaggio."
@@ -2130,3 +2138,6 @@ _moderationLogTypes:
   unmarkSensitiveDriveFile: "File nel Drive segnato come non esplicito"
   resolveAbuseReport: "Segnalazione risolta"
   createInvitation: "Genera codice di invito"
+  createAd: "Banner creato"
+  deleteAd: "Banner eliminato"
+  updateAd: "Banner aggiornato"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index c4594987296ce2af7c60e3096672044adab8419a..9adc4381a71618e82c31b4f8c67ae47633b41bc3 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1126,6 +1126,15 @@ edited: "編集済み"
 notificationRecieveConfig: "通知の受信設定"
 mutualFollow: "相互フォロー"
 fileAttachedOnly: "ファイル付きのみ"
+showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
+hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
+externalServices: "外部サービス"
+impressum: "運営者情報"
+impressumUrl: "運営者情報URL"
+impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。"
+privacyPolicy: "プライバシーポリシー"
+privacyPolicyUrl: "プライバシーポリシーURL"
+tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
 
 _announcement:
   forExistingUsers: "既存ユーザーのみ"
@@ -1463,7 +1472,6 @@ _role:
     gtlAvailable: "グローバルタイムラインの閲覧"
     ltlAvailable: "ローカルタイムラインの閲覧"
     canPublicNote: "パブリック投稿の許可"
-    canEditNote: "ノートの編集"
     canInvite: "サーバー招待コードの発行"
     inviteLimit: "招待コードの作成可能数"
     inviteLimitCycle: "招待コードの発行間隔"
@@ -1538,6 +1546,10 @@ _ad:
   reduceFrequencyOfThisAd: "この広告の表示頻度を下げる"
   hide: "表示しない"
   timezoneinfo: "曜日はサーバーのタイムゾーンを元に指定されます。"
+  adsSettings: "広告配信設定"
+  notesPerOneAd: "リアルタイム更新中に広告を配信する間隔(ノートの個数)"
+  setZeroToDisable: "0でリアルタイム更新時の広告配信を無効"
+  adsTooClose: "広告の配信間隔が極めて短いため、ユーザー体験が著しく損われる可能性があります。"
 
 _forgotPassword:
   enterEmail: "アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。"
@@ -1636,11 +1648,6 @@ _wordMute:
   muteWords: "ミュートするワード"
   muteWordsDescription: "スペースで区切るとAND指定になり、改行で区切るとOR指定になります。"
   muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
-  softDescription: "指定した条件のノートをタイムラインから隠します。"
-  hardDescription: "指定した条件のノートをタイムラインに追加しないようにします。追加されなかったノートは、条件を変更しても除外されたままになります。"
-  soft: "ソフト"
-  hard: "ハード"
-  mutedNotes: "ミュートされたノート"
 
 _instanceMute:
   instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。"
@@ -1707,9 +1714,6 @@ _theme:
     infoFg: "情報の文字"
     infoWarnBg: "警告の背景"
     infoWarnFg: "警告の文字"
-    cwBg: "CW ボタンの背景"
-    cwFg: "CW ボタンの文字"
-    cwHoverBg: "CW ボタンの背景 (ホバー)"
     toastBg: "通知トーストの背景"
     toastFg: "通知トーストの文字"
     buttonBg: "ボタンの背景"
@@ -1728,8 +1732,6 @@ _sfx:
   note: "ノート"
   noteMy: "ノート(自分)"
   notification: "通知"
-  chat: "チャット"
-  chatBg: "チャット(バックグラウンド)"
   antenna: "アンテナ受信"
   channel: "チャンネル通知"
 
@@ -2201,3 +2203,12 @@ _moderationLogTypes:
   createAd: "広告を作成"
   deleteAd: "広告を削除"
   updateAd: "広告を更新"
+
+_fileViewer:
+  title: "ファイルの詳細"
+  type: "ファイルタイプ"
+  size: "ファイルサイズ"
+  url: "URL"
+  uploadedAt: "追加日"
+  attachedNotes: "添付されているノート"
+  thisPageCanBeSeenFromTheAuthor: "このページは、このファイルをアップロードしたユーザーしか閲覧できません。"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index bf945088f0ff74aef9932de28b7babe263948c32..5efd8cd1191ad5e836e430bcc09d71ac75c54b9d 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1586,11 +1586,6 @@ _wordMute:
   muteWords: "ミュートするワード"
   muteWordsDescription: "スペースで区切るとAND指定になって、改行で区切るとOR指定になるで。"
   muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になるで。"
-  softDescription: "指定した条件のノートをタイムラインから隠すで。"
-  hardDescription: "指定した条件のノートをタイムラインに追加しないようにするで。追加せーへんかったかったノートは、条件を変えても除外されたままになるで。"
-  soft: "ソフト"
-  hard: "ハード"
-  mutedNotes: "ミュートされたノート"
 _instanceMute:
   instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したインスタンスの全てのノートとRenoteをミュートにするで。"
   instanceMuteDescription2: "改行で区切って設定するんやで"
@@ -1654,9 +1649,6 @@ _theme:
     infoFg: "情報の文字"
     infoWarnBg: "警告の背景"
     infoWarnFg: "警告の文字"
-    cwBg: "CW ボタンの背景"
-    cwFg: "CW ボタンの文字"
-    cwHoverBg: "CW ボタンの背景 (ホバー)"
     toastBg: "通知トーストの背景"
     toastFg: "通知トーストの文字"
     buttonBg: "ボタンの背景"
@@ -1674,8 +1666,6 @@ _sfx:
   note: "ノート"
   noteMy: "ノート(自分)"
   notification: "通知"
-  chat: "チャット"
-  chatBg: "チャット(バックグラウンド)"
   antenna: "アンテナ受信"
   channel: "チャンネル通知"
 _ago:
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index af7afb2c3ecd766faccb3ff686ea545a168f8c46..d5c346717b6b3ce8b42841089707dfc0195ef5ff 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1600,11 +1600,6 @@ _wordMute:
   muteWords: "뮤트할 단어"
   muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다."
   muteWordsDescription2: "정규 표현식을 사용하려면 키워드를 빗금표(/)로 감싸 주세요."
-  softDescription: "지정한 조건의 노트를 타임라인에서 숨깁니다."
-  hardDescription: "지정한 조건의 노트를 타임라인에 추가하지 않습니다. 타임라인에 추가되지 않은 노트는 조건을 변경해도 표시되지 않습니다."
-  soft: "보통"
-  hard: "보다 높은 수준"
-  mutedNotes: "뮤트된 노트"
 _instanceMute:
   instanceMuteDescription: "뮤트한 서버에서 오는 답글을 포함한 모든 노트와 Renote를 뮤트합니다."
   instanceMuteDescription2: "한 줄에 하나씩 입력해 주세요"
@@ -1668,9 +1663,6 @@ _theme:
     infoFg: "정보창 텍스트"
     infoWarnBg: "경고창 배경"
     infoWarnFg: "경고창 텍스트"
-    cwBg: "CW 버튼 배경"
-    cwFg: "CW 버튼 텍스트"
-    cwHoverBg: "CW 버튼 배경 (호버)"
     toastBg: "알림창 배경"
     toastFg: "알림창 텍스트"
     buttonBg: "버튼 배경"
@@ -1688,8 +1680,6 @@ _sfx:
   note: "새 노트"
   noteMy: "내 노트"
   notification: "알림"
-  chat: "대화"
-  chatBg: "대화 (백그라운드)"
   antenna: "안테나 수신"
   channel: "채널 알림"
 _ago:
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 22cb5857f901f98fac6d9d74aab58dcf199c409f..b22e047cfa7d3faa9865fc3d6f261e24549f5a06 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -407,7 +407,6 @@ _theme:
 _sfx:
   note: "ບັນທຶກ"
   notification: "ການແຈ້ງເຕືອນ"
-  chat: "ແຊ໋ດ"
 _2fa:
   renewTOTPCancel: "ບໍ່​ແມ່ນ​ຕອນ​ນີ້"
 _widgets:
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index fd9ffa33f294d6d9d28d140902d6724d4b914a4e..6f789dff10858714d851ca6ab43d086bda9c3b9b 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -438,7 +438,6 @@ _theme:
 _sfx:
   note: "Notities"
   notification: "Meldingen"
-  chat: "Chat"
 _2fa:
   renewTOTPCancel: "Nee, bedankt"
 _widgets:
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 00f22c0c4ffeeada5381958fe6ad2f036d0fac8e..d99c61c1dd75ff9aaf8884eb03b3af63ff390254 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -575,9 +575,6 @@ _channel:
   nameAndDescription: "Navn og beskrivelse"
 _menuDisplay:
   hide: "Skjul"
-_wordMute:
-  soft: "Myk"
-  hard: "Hard"
 _theme:
   description: "Beskrivelse"
   color: "Farge"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 1c7ebe81085b9767811d991e2cba98ce1fb344d6..f88055cc293c91392029a20d544d64c84514f40e 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -982,9 +982,6 @@ _menuDisplay:
 _wordMute:
   muteWords: "SÅ‚owo do wyciszenia"
   muteWordsDescription2: "Otocz słowa kluczowe ukośnikami, aby używać wyrażeń regularnych."
-  soft: "Łagodny"
-  hard: "Twardy"
-  mutedNotes: "Wyciszone wpisy"
 _instanceMute:
   title: "Ukrywa wpisy z wymienionych instancji."
   heading: "Lista instancji do wyciszenia"
@@ -1046,9 +1043,6 @@ _theme:
     infoFg: "Tekst informacji"
     infoWarnBg: "Tło ostrzeżenia"
     infoWarnFg: "Tekst ostrzeżenia"
-    cwBg: "TÅ‚o CW"
-    cwFg: "Tekst CW"
-    cwHoverBg: "TÅ‚o CW (po najechaniu)"
     toastBg: "Tło powiadomień"
     toastFg: "Tekst powiadomień"
     buttonBg: "TÅ‚o przycisku"
@@ -1066,8 +1060,6 @@ _sfx:
   note: "Wpisy"
   noteMy: "Mój wpis"
   notification: "Powiadomienia"
-  chat: "Wiadomości"
-  chatBg: "Rozmowy (tło)"
   antenna: "Anteny"
   channel: "Powiadomienia kanału"
 _ago:
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index f9e777bc75739efa47fd885eaf31cb19e09009a9..23864df1b8f50791b8c18a0d86c93687f04a55d1 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -1320,7 +1320,6 @@ _theme:
 _sfx:
   note: "Posts"
   notification: "Notificações"
-  chat: "Chat"
 _ago:
   invalid: "Não há nada aqui"
 _timelineTutorial:
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index 51c33085afed9b3008f4ca5d17ffe63482b4e283..77bccb7e6b1312b819df9ceb3c4e5daf35bec763 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -647,7 +647,6 @@ _theme:
 _sfx:
   note: "Note"
   notification: "Notificări"
-  chat: "Chat"
 _ago:
   invalid: "Nu e nimic de văzut aici"
 _widgets:
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 937158978d1712ab31d161083281fc6557990ccc..19e4baccb1a93d749f731c9e101af61d8991561e 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1488,11 +1488,6 @@ _wordMute:
   muteWords: "Скрыть слово"
   muteWordsDescription: "Пишите слова через пробел в одной строке, чтобы фильтровать их появление вместе; а если хотите фильтровать любое из них, пишите в отдельных строках."
   muteWordsDescription2: "Здесь можно использовать регулярные выражения — просто заключите их между двумя дробными чертами (/)."
-  softDescription: "Соответствующие условиям заметки будут спрятаны из вашей ленты."
-  hardDescription: "Соответстующие условиям заметки вообще не будут попадать в вашу ленту. Даже если вы поменяете условия, отсеенные таким образом заметки уже не появятся."
-  soft: "Мягко"
-  hard: "Жёстко"
-  mutedNotes: "Скрытые заметки"
 _instanceMute:
   instanceMuteDescription: "Заметки и репосты с указанных здесь инстансов, а также ответы пользователям оттуда же не будут отображаться."
   instanceMuteDescription2: "Пишите каждый инстанс на отдельной строке"
@@ -1556,9 +1551,6 @@ _theme:
     infoFg: "Текст сообщения"
     infoWarnBg: "Фон предупреждения"
     infoWarnFg: "Текст предупреждения"
-    cwBg: "Фон предупреждения о содержимом"
-    cwFg: "Текст предупреждения о содержимом"
-    cwHoverBg: "Фон предупреждения о содержимом (под указателем)"
     toastBg: "Фон оповещения"
     toastFg: "Текст оповещения"
     buttonBg: "Фон кнопки"
@@ -1576,8 +1568,6 @@ _sfx:
   note: "Заметки"
   noteMy: "Собственные заметки"
   notification: "Уведомления"
-  chat: "Сообщения"
-  chatBg: "Сообщения (фон)"
   antenna: "Антенна"
   channel: "Канал"
 _ago:
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index e44aaafc0ad67bac942e23c51acd3100a9e13ce1..181e725d758ac1f2edbb9fff4c96a00ead023115 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -1039,11 +1039,6 @@ _wordMute:
   muteWords: "Umlčané slová"
   muteWordsDescription: "Medzerami oddeľte pre podmienku AND a novými riadkami pre podmienku OR."
   muteWordsDescription2: "Regulárne výrazy sa použijú keď použijete okolo lomítka."
-  softDescription: "Skryje poznámky z časovej osi, ktoré spĺňajú podmienky."
-  hardDescription: "Zabráni poznámky spĺňajúce množinu podmienok, aby boli pridané do časovej osi. Navyše tieto poznámky nepribudnú v časovej osi ani keď sa podmienky zmenia."
-  soft: "Mäkké"
-  hard: "Tvrdé"
-  mutedNotes: "Umlčané poznámky"
 _instanceMute:
   instanceMuteDescription: "Toto umlčí všetky poznámky/preposlania zo zoznamu serverov, vrátane tých, na ktoré používatelia odpovedajú z umlčaného servera."
   instanceMuteDescription2: "Oddeľte novými riadkami"
@@ -1107,9 +1102,6 @@ _theme:
     infoFg: "Informačný text"
     infoWarnBg: "Pozadie varovania"
     infoWarnFg: "Text varovania"
-    cwBg: "CW pozadie tlačidla"
-    cwFg: "CW text tlačidla"
-    cwHoverBg: "CW pozadie tlačidla (pod kurzorom)"
     toastBg: "Pozadie upozornenia"
     toastFg: "Text upozornenia"
     buttonBg: "Pozadie tlačidla"
@@ -1127,8 +1119,6 @@ _sfx:
   note: "Poznámky"
   noteMy: "Vlastná poznámka"
   notification: "Oznámenia"
-  chat: "Chat"
-  chatBg: "Chat (pozadie)"
   antenna: "Antény"
   channel: "Upozornenia kanála"
 _ago:
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 62e7d412abd563e5d190723d15b7c29a9c52dc52..92678afef8063643af6fbdd07c8aafbe147c4541 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -507,7 +507,6 @@ _theme:
 _sfx:
   note: "Noter"
   notification: "Notifikationer"
-  chat: "Chatt"
   antenna: "Antenner"
 _2fa:
   renewTOTPCancel: "Nej tack"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index c2adcf8ec59688120344424c230a86c299c143a6..1c655f5886d1c3a610f07d1d3bf22b10548749f9 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -1150,6 +1150,7 @@ _serverRules:
   description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
 _serverSettings:
   iconUrl: "ไอคอน URL"
+  manifestJsonOverride: "manifest.json โอเวอร์ลาย"
   shortName: "ชื่อย่อ"
 _accountMigration:
   moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
@@ -1407,6 +1408,7 @@ _achievements:
       flavor: "Misskey-Misskey La-Tu-Ma"
     _smashTestNotificationButton:
       title: "ทดสอบโอเวอร์โฟลว์"
+      description: "ทดสอบการแจ้งเตือนทริกเกอร์ซ้ำๆ ภายในระยะเวลาอันสั้นๆ"
 _role:
   new: "บทบาทใหม่"
   edit: "แก้ไขบทบาท"
@@ -1445,7 +1447,6 @@ _role:
     gtlAvailable: "การดูไทม์ไลน์ทั่วโลก"
     ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น"
     canPublicNote: "สามารถส่งโน้ตสาธารณะ"
-    canEditNote: "กำลังแก้ไขโน้ต"
     canInvite: "สร้างรหัสเชิญอินสแตนซ์"
     inviteLimit: "จำกัดการเชิญ"
     inviteLimitCycle: "จำกัดการเชิญไว้คูลดาวน์"
@@ -1465,6 +1466,7 @@ _role:
     descriptionOfRateLimitFactor: "ขีดจํากัดอัตราที่ต่ำกว่ามีข้อจํากัดน้อยกว่าข้อจํากัดที่สูงกว่า"
     canHideAds: "ซ่อนโฆษณา"
     canSearchNotes: "การใช้การค้นหาโน้ต"
+    canUseTranslator: "การใช้งานแปล"
   _condition:
     isLocal: "ผู้ใช้ภายใน"
     isRemote: "ผู้ใช้ระยะไกล"
@@ -1598,11 +1600,6 @@ _wordMute:
   muteWords: "ปิดเสียงคำ"
   muteWordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ"
   muteWordsDescription2: "ล้อมรอบคีย์เวิร์ดด้วยเครื่องหมายทับเพื่อใช้นิพจน์ทั่วไป"
-  softDescription: "ซ่อนโน้ตให้ตรงตามเงื่อนไขที่ตั้งไว้จากไทม์ไลน์"
-  hardDescription: "ป้องกันไม่ให้โน้ตย่อที่ตรงตามเงื่อนไขที่ตั้งไว้ไม่ให้ถูกเพิ่มลงในไทม์ไลน์ นอกจากนี้ โน้ตเหล่านี้จะไม่ถูกเพิ่มลงในไทม์ไลน์แม้ว่าจะมีการเปลี่ยนแปลงเงื่อนไขยังไงก็ตาม"
-  soft: "ซอฟ"
-  hard: "ยาก"
-  mutedNotes: "ปิดเสียงโน้ต"
 _instanceMute:
   instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง"
   instanceMuteDescription2: "คั่นด้วยการขึ้นบรรทัดใหม่"
@@ -1666,9 +1663,6 @@ _theme:
     infoFg: "ข้อความข้อมูล"
     infoWarnBg: "คำเตือนพื้นหลัง"
     infoWarnFg: "คำเตือนข้อความ"
-    cwBg: "ปุ่ม CW พื้นหลัง"
-    cwFg: "ปุ่ม CW ข้อความ"
-    cwHoverBg: "ปุ่ม CW พื้นหลัง (โฮเวอร์)"
     toastBg: "ประวัติการแจ้งเตือน"
     toastFg: "ข้อความแจ้งเตือน"
     buttonBg: "ปุ่มพื้นหลัง"
@@ -1686,8 +1680,6 @@ _sfx:
   note: "หมายเหตุ"
   noteMy: "โน้ตของตัวเอง"
   notification: "การเเจ้งเตือน"
-  chat: "แชท"
-  chatBg: "แชท (พื้นหลัง)"
   antenna: "เสาอากาศ"
   channel: "การแจ้งเตือนช่อง"
 _ago:
@@ -1792,6 +1784,7 @@ _antennaSources:
   homeTimeline: "โน้ตจากผู้ใช้ที่ติดตาม"
   users: "โน้ตจากผู้ใช้ที่เฉพาะเจาะจง"
   userList: "โน้ตจากรายชื่อผู้ใช้ที่ระบุ"
+  userBlacklist: "โน้ตทั้งหมดยกเว้นโน้ตของผู้ใช้ที่ต้องระบุเจาะจงตั้งแต่หนึ่งรายขึ้นไป"
 _weekday:
   sunday: "วันอาทิตย์"
   monday: "วันจันทร์"
@@ -1891,6 +1884,7 @@ _profile:
   metadataContent: "เนื้อหา"
   changeAvatar: "เปลี่ยนอวาตาร์"
   changeBanner: "เปลี่ยนแบนเนอร์"
+  verifiedLinkDescription: "โดยการป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณตรงนี้ ส่วนไอคอนการยืนยันความเป็นเจ้าของนั้นก็สามารถแสดงถัดจากฟิลด์ได้นะ"
 _exportOrImport:
   allNotes: "โน้ตทั้งหมด"
   favoritedNotes: "บันทึกที่ชื่นชอบ"
@@ -2104,7 +2098,17 @@ _moderationLogTypes:
   updateUserNote: "อัปเดตโน้ตการกลั่นกรองแล้ว"
   deleteDriveFile: "ลบไฟล์ออกแล้ว"
   deleteNote: "ลบโน้ตออกแล้ว"
+  createGlobalAnnouncement: "สร้างประกาศทั่วโลกแล้ว"
+  createUserAnnouncement: "สร้างประกาศผู้ใช้แล้ว"
+  updateGlobalAnnouncement: "อัปเดตประกาศทั่วโลกแล้ว"
+  updateUserAnnouncement: "อัปเดตประกาศผู้ใช้แล้ว"
+  deleteGlobalAnnouncement: "ลบประกาศทั่วโลกออกแล้ว"
+  deleteUserAnnouncement: "ลบประกาศผู้ใช้ออกแล้ว"
   resetPassword: "รีเซ็ตรหัสผ่าน"
+  suspendRemoteInstance: "อินสแตนซ์ระยะไกลถูกระงับ"
+  unsuspendRemoteInstance: "อินสแตนซ์ระยะไกลเลิกการระงับ"
+  markSensitiveDriveFile: "ทำเครื่องหมายไฟล์บอกว่าละเอียดอ่อน"
+  unmarkSensitiveDriveFile: "ยกเลิกทำเครื่องหมายไฟล์ว่าละเอียดอ่อน"
   resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
   createInvitation: "สร้างคำเชิญ"
   createAd: "สร้างโฆษณาแล้ว"
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 1111c23091acc5e25fbe79fce055f57f8090cf25..90bee48a1fdbfd5937dc2ec3aafadf10eef90110 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -386,7 +386,6 @@ _theme:
 _sfx:
   note: "notlar"
   notification: "Bildirim"
-  chat: "Mesajlar"
 _2fa:
   renewTOTPCancel: "Hayır, teşekkürler"
 _permissions:
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 09b3eba74595386f1cd99cead79aa36663244f12..8d843d67f8b410d8df78243d1b3b047740b976d6 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -1233,11 +1233,6 @@ _wordMute:
   muteWords: "Заглушені слова"
   muteWordsDescription: "Розділення ключових слів пробілами для \"І\" або з нової лінійки для \"АБО\""
   muteWordsDescription2: "Для використання RegEx, ключові слова потрібно вписати поміж слешів \"/\"."
-  softDescription: "Приховати записи які відповідають критеріям зі стрічки подій."
-  hardDescription: "Приховати записи які відповідають критеріям зі стрічки подій. Також приховані записи не будуть додані до стрічки подій навіть якщо критерії буде змінено."
-  soft: "М'яко"
-  hard: "Жорстко"
-  mutedNotes: "Заблоковані нотатки"
 _instanceMute:
   instanceMuteDescription2: "Розділяйте новими рядками"
   title: "Приховує нотатки з перелічених інстансів."
@@ -1295,9 +1290,6 @@ _theme:
     infoFg: "Текст інформації"
     infoWarnBg: "Фон попередження"
     infoWarnFg: "Текст попередження"
-    cwBg: "Фон чутливого змісту"
-    cwFg: "Текст чутливого змісту"
-    cwHoverBg: "Фон чутливого змісту (при наведенні)"
     toastBg: "Фон повідомлення"
     toastFg: "Текст повідомлення"
     buttonBg: "Фон кнопки"
@@ -1315,8 +1307,6 @@ _sfx:
   note: "Нотатки"
   noteMy: "Мої нотатки"
   notification: "Сповіщення"
-  chat: "Чати"
-  chatBg: "Чати (фон)"
   antenna: "Прийом антени"
   channel: "Повідомлення каналу"
 _ago:
diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml
index 726333958b01b18652135f08a386af186139c6ea..3a9e6ec5e724ca17c3916aed3e05261090a43c8c 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -910,7 +910,6 @@ _theme:
 _sfx:
   note: "Qaydlar"
   notification: "Xabarnomalar"
-  chat: "Suhbat"
 _ago:
   minutesAgo: "{n} daqiqa oldin"
   hoursAgo: "{n} soat oldin"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 3b34e4711cad782b5f6a3660d3ca36e0a7436687..b8a77a92004360150a809a6594c1ead736204f40 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1404,11 +1404,6 @@ _wordMute:
   muteWords: "Ẩn từ ngữ"
   muteWordsDescription: "Separate with spaces for an AND condition or with line breaks for an OR condition."
   muteWordsDescription2: "Bao quanh các từ khóa bằng dấu gạch chéo để sử dụng cụm từ thông dụng."
-  softDescription: "Ẩn các tút phù hợp điều kiện đã đặt khỏi bảng tin."
-  hardDescription: "Ngăn các tút đáp ứng các điều kiện đã đặt xuất hiện trên bảng tin. Lưu ý, những tút này sẽ không được thêm vào bảng tin ngay cả khi các điều kiện được thay đổi."
-  soft: "Yếu"
-  hard: "Mạnh"
-  mutedNotes: "Những tút đã ẩn"
 _instanceMute:
   instanceMuteDescription: "Thao tác này sẽ ẩn mọi tút/lượt đăng lại từ các máy chủ được liệt kê, bao gồm cả những tút  dạng trả lời từ máy chủ bị ẩn."
   instanceMuteDescription2: "Tách bằng cách xuống dòng"
@@ -1472,9 +1467,6 @@ _theme:
     infoFg: "Chữ thông tin"
     infoWarnBg: "Nền cảnh báo"
     infoWarnFg: "Chữ cảnh báo"
-    cwBg: "Nền nút nội dung ẩn"
-    cwFg: "Chữ nút nội dung ẩn"
-    cwHoverBg: "Nền nút nội dung ẩn (Chạm)"
     toastBg: "Nền thông báo"
     toastFg: "Chữ thông báo"
     buttonBg: "Nền nút"
@@ -1492,8 +1484,6 @@ _sfx:
   note: "Tút"
   noteMy: "Tút của tôi"
   notification: "Thông báo"
-  chat: "Trò chuyện"
-  chatBg: "Chat (Nền)"
   antenna: "Trạm phát sóng"
   channel: "Kênh"
 _ago:
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index a04697e480b8b2601b36cc99586ddb90f99236b7..dfc4ccb688ec4068a3b23411e2e9a7183a6cad20 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -1126,6 +1126,8 @@ edited: "已编辑"
 notificationRecieveConfig: "通知接收设置"
 mutualFollow: "互相关注"
 fileAttachedOnly: "仅限媒体"
+showRepliesToOthersInTimeline: "在时间线上显示给其他人的回复"
+hideRepliesToOthersInTimeline: "在时间线上隐藏给其他人的回复"
 _announcement:
   forExistingUsers: "仅限现有用户"
   forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
@@ -1456,7 +1458,6 @@ _role:
     gtlAvailable: "查看全局时间线"
     ltlAvailable: "查看本地时间线"
     canPublicNote: "允许公开发帖"
-    canEditNote: "编辑帖子"
     canInvite: "发放服务器邀请码"
     inviteLimit: "可发行邀请码的数量"
     inviteLimitCycle: "邀请码的发行间隔"
@@ -1609,11 +1610,6 @@ _wordMute:
   muteWords: "禁用词"
   muteWordsDescription: "AND 条件用空格分隔,OR 条件用换行符分隔。"
   muteWordsDescription2: "正则表达式用斜线包裹"
-  softDescription: "隐藏时间线中指定条件的帖子。"
-  hardDescription: "防止将具有指定条件的帖子添加到时间线。 即使您更改条件,未添加的帖文也会被排除在外。"
-  soft: "软屏蔽"
-  hard: "硬屏蔽"
-  mutedNotes: "被屏蔽的帖子"
 _instanceMute:
   instanceMuteDescription: "屏蔽服务器中的所有帖子和转帖,包括这些服务器上的用户回复。"
   instanceMuteDescription2: "一行一个"
@@ -1677,9 +1673,6 @@ _theme:
     infoFg: "信息文本"
     infoWarnBg: "警告背景"
     infoWarnFg: "警告文本"
-    cwBg: "隐藏内容按钮背景"
-    cwFg: "隐藏内容按钮文本"
-    cwHoverBg: "隐藏内容按钮背景(悬停)"
     toastBg: "Toast 通知背景"
     toastFg: "Toast 通知文本"
     buttonBg: "按钮背景"
@@ -1697,8 +1690,6 @@ _sfx:
   note: "帖子"
   noteMy: "我的帖子"
   notification: "通知"
-  chat: "聊天"
-  chatBg: "聊天背景"
   antenna: "天线接收"
   channel: "频道通知"
 _ago:
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index c0bf1f7d1bc39bda542e6d002467c94db8d43365..e715cae547bc60602092727a28b4b07409ce70c7 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -156,7 +156,7 @@ emojiUrl: "表情符號 URL"
 addEmoji: "新增表情符號"
 settingGuide: "推薦設定"
 cacheRemoteFiles: "快取遠端檔案"
-cacheRemoteFilesDescription: "啟用此設定後,遠端檔案會被快取在本伺服器的儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true,以便產生圖片的縮圖並保護使用者的隱私,。"
+cacheRemoteFilesDescription: "啟用此設定後,遠端檔案會被快取在本伺服器的儲存空間中。雖然顯示圖片會變快,但會消耗較多伺服器的儲存空間。至於要快取遠端使用者到什麼程度,是依照角色的雲端硬碟容量而定。當超過這個限制時,從較舊的檔案開始自快取中刪除並改為連結。關閉這個設定時,遠端檔案從一開始就維持連結的方式,但建議將 default.yml 的 proxyRemoteFiles 設為 true,以便產生圖片的縮圖並保護使用者的隱私。"
 youCanCleanRemoteFilesCache: "按檔案管理的🗑️按鈕,可將快取全部刪除。"
 cacheRemoteSensitiveFiles: "快取遠端的敏感檔案"
 cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取遠端的敏感檔案,而是直接連結。"
@@ -1123,7 +1123,18 @@ authenticationRequiredToContinue: "請於繼續前完成驗證"
 dateAndTime: "日期與時間"
 showRenotes: "顯示轉發貼文"
 edited: "已編輯"
+notificationRecieveConfig: "接受通知的設定"
 mutualFollow: "互相追隨"
+fileAttachedOnly: "包含附件"
+showRepliesToOthersInTimeline: "在時間軸上顯示給其他人的回覆"
+hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
+externalServices: "外部服務"
+impressum: "營運者資訊"
+impressumUrl: "營運者資訊網址"
+impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。"
+privacyPolicy: "隱私政策"
+privacyPolicyUrl: "隱私政策網址"
+tosAndPrivacyPolicy: "服務條款和隱私政策"
 _announcement:
   forExistingUsers: "僅限既有的使用者"
   forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
@@ -1454,7 +1465,6 @@ _role:
     gtlAvailable: "瀏覽全域時間軸"
     ltlAvailable: "瀏覽本地時間軸"
     canPublicNote: "允許公開貼文"
-    canEditNote: "允許編輯貼文"
     canInvite: "發行實例邀請碼"
     inviteLimit: "可建立邀請碼的數量"
     inviteLimitCycle: "邀請碼的發放間隔"
@@ -1474,6 +1484,7 @@ _role:
     descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。"
     canHideAds: "不顯示廣告"
     canSearchNotes: "可否搜尋貼文"
+    canUseTranslator: "使用翻譯功能"
   _condition:
     isLocal: "本地使用者"
     isRemote: "遠端使用者"
@@ -1522,6 +1533,10 @@ _ad:
   reduceFrequencyOfThisAd: "降低此廣告的頻率 "
   hide: "隱藏"
   timezoneinfo: "星期幾是由伺服器的時區指定的。"
+  adsSettings: "廣告投放設定"
+  notesPerOneAd: "即時更新中投放廣告的間隔(貼文數)"
+  setZeroToDisable: "設為 0 則在即時更新時不投放廣告"
+  adsTooClose: "由於廣告投放的間隔極短,可能會嚴重影響使用者體驗。"
 _forgotPassword:
   enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
   ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。 "
@@ -1607,11 +1622,6 @@ _wordMute:
   muteWords: "加入靜音文字"
   muteWordsDescription: "空格代表「以及」(AND),換行代表「或者」(OR)。"
   muteWordsDescription2: "用斜線包圍關鍵字代表正規表達式。"
-  softDescription: "隱藏時間軸中符合特定條件的貼文。"
-  hardDescription: "符合特定條件的貼文將不會新增至時間軸。 即使您更改條件,未被新增的貼文也會被排除在外。"
-  soft: "軟性靜音"
-  hard: "硬性靜音"
-  mutedNotes: "已靜音的貼文"
 _instanceMute:
   instanceMuteDescription: "包括對被靜音實例上的使用者的回覆,被設定的實例上所有貼文及轉發都會被靜音。"
   instanceMuteDescription2: "設定時以換行進行分隔"
@@ -1675,9 +1685,6 @@ _theme:
     infoFg: "資訊內容"
     infoWarnBg: "警告背景"
     infoWarnFg: "警告文字"
-    cwBg: "隱藏內容按鈕背景"
-    cwFg: "隱藏內容按鈕文字"
-    cwHoverBg: "隱藏內容按鈕背景(懸浮)"
     toastBg: "通知背景"
     toastFg: "通知文本"
     buttonBg: "按鈕背景"
@@ -1695,8 +1702,6 @@ _sfx:
   note: "貼文"
   noteMy: "我的貼文"
   notification: "通知"
-  chat: "聊天"
-  chatBg: "聊天背景"
   antenna: "天線接收"
   channel: "頻道通知"
 _ago:
diff --git a/package.json b/package.json
index 2259ede6d55ef8ad60134b48ad0cb5cfad6c024a..e94852616e9db99c23235d2ca91c24165c3ac7a2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "2023.9.3",
+	"version": "2023.10.0",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
@@ -47,15 +47,15 @@
 		"cssnano": "6.0.1",
 		"js-yaml": "4.1.0",
 		"postcss": "8.4.31",
-		"terser": "5.20.0",
+		"terser": "5.21.0",
 		"typescript": "5.2.2"
 	},
 	"devDependencies": {
-		"@typescript-eslint/eslint-plugin": "6.7.3",
-		"@typescript-eslint/parser": "6.7.3",
+		"@typescript-eslint/eslint-plugin": "6.7.5",
+		"@typescript-eslint/parser": "6.7.5",
 		"cross-env": "7.0.3",
 		"cypress": "13.3.0",
-		"eslint": "8.50.0",
+		"eslint": "8.51.0",
 		"start-server-and-test": "2.0.1"
 	},
 	"optionalDependencies": {
diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs
index 6b1afec734923c16cc568f5cbac13a5238c5bf79..97d777c86287093f8b1f7ccaa73be9dbbd473701 100644
--- a/packages/backend/jest.config.cjs
+++ b/packages/backend/jest.config.cjs
@@ -216,4 +216,6 @@ module.exports = {
 	maxWorkers: 1, // Make it use worker (that can be killed and restarted)
 	logHeapUsage: true, // To debug when out-of-memory happens on CI
 	workerIdleMemoryLimit: '1GiB', // Limit the worker to 1GB (GitHub Workflows dies at 2GB)
+
+	maxConcurrency: 32,
 };
diff --git a/packages/backend/migration/1696003580220-AddSomeUrls.js b/packages/backend/migration/1696003580220-AddSomeUrls.js
new file mode 100644
index 0000000000000000000000000000000000000000..683aa5eeed579521989e66bbb0536ae0e03d99fa
--- /dev/null
+++ b/packages/backend/migration/1696003580220-AddSomeUrls.js
@@ -0,0 +1,17 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AddSomeUrls1696003580220 {
+    name = 'AddSomeUrls1696003580220'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "impressumUrl" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "privacyPolicyUrl" character varying(1024)`);
+    }
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "impressumUrl"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "privacyPolicyUrl"`);
+    }
+}
diff --git a/packages/backend/migration/1696222183852-withReplies.js b/packages/backend/migration/1696222183852-withReplies.js
new file mode 100644
index 0000000000000000000000000000000000000000..9f65d5f6a1629a142daa40b9bee4b04ff6a093ff
--- /dev/null
+++ b/packages/backend/migration/1696222183852-withReplies.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class WithReplies1696222183852 {
+    name = 'WithReplies1696222183852'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "following" ADD "withReplies" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`ALTER TABLE "user_list_joining" ADD "withReplies" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
+        await queryRunner.query(`ALTER TABLE "user_list_joining" DROP COLUMN "withReplies"`);
+        await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "withReplies"`);
+    }
+}
diff --git a/packages/backend/migration/1696323464251-user-list-membership.js b/packages/backend/migration/1696323464251-user-list-membership.js
new file mode 100644
index 0000000000000000000000000000000000000000..7534040c4c9bce481af8cf217b7452724d7a1e24
--- /dev/null
+++ b/packages/backend/migration/1696323464251-user-list-membership.js
@@ -0,0 +1,11 @@
+export class UserListMembership1696323464251 {
+    name = 'UserListMembership1696323464251'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_joining" RENAME TO "user_list_membership"`);
+    }
+
+    async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "user_list_membership" RENAME TO "user_list_joining"`);
+    }
+}
diff --git a/packages/backend/migration/1696331570827-hibernation.js b/packages/backend/migration/1696331570827-hibernation.js
new file mode 100644
index 0000000000000000000000000000000000000000..119d35913f7b8bc19b93f35fb66a1a9e37013478
--- /dev/null
+++ b/packages/backend/migration/1696331570827-hibernation.js
@@ -0,0 +1,17 @@
+export class Hibernation1696331570827 {
+    name = 'Hibernation1696331570827'
+
+    async up(queryRunner) {
+				await queryRunner.query(`DROP INDEX "public"."IDX_d74d8ab5efa7e3bb82825c0fa2"`);
+        await queryRunner.query(`ALTER TABLE "user" ADD "isHibernated" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`ALTER TABLE "following" ADD "isFollowerHibernated" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`CREATE INDEX "IDX_ce62b50d882d4e9dee10ad0d2f" ON "following" ("followeeId", "followerHost", "isFollowerHibernated") `);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_ce62b50d882d4e9dee10ad0d2f"`);
+        await queryRunner.query(`ALTER TABLE "following" DROP COLUMN "isFollowerHibernated"`);
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "isHibernated"`);
+        await queryRunner.query(`CREATE INDEX "IDX_d74d8ab5efa7e3bb82825c0fa2" ON "following" ("followeeId", "followerHost") `);
+    }
+}
diff --git a/packages/backend/migration/1696332072038-clean.js b/packages/backend/migration/1696332072038-clean.js
new file mode 100644
index 0000000000000000000000000000000000000000..97dba655f46c441179d9b5e1270d494d24703cc0
--- /dev/null
+++ b/packages/backend/migration/1696332072038-clean.js
@@ -0,0 +1,33 @@
+export class Clean1696332072038 {
+    name = 'Clean1696332072038'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_d844bfc6f3f523a05189076efaa"`);
+        await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_605472305f26818cc93d1baaa74"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_d844bfc6f3f523a05189076efa"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_605472305f26818cc93d1baaa7"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_90f7da835e4c10aca6853621e1"`);
+        await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }'`);
+        await queryRunner.query(`COMMENT ON COLUMN "user_list_membership"."createdAt" IS 'The created date of the UserListMembership.'`);
+        await queryRunner.query(`CREATE INDEX "IDX_021015e6683570ae9f6b0c62be" ON "user_list_membership" ("userId") `);
+        await queryRunner.query(`CREATE INDEX "IDX_cddcaf418dc4d392ecfcca842a" ON "user_list_membership" ("userListId") `);
+        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_e4f3094c43f2d665e6030b0337" ON "user_list_membership" ("userId", "userListId") `);
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_021015e6683570ae9f6b0c62bee" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_cddcaf418dc4d392ecfcca842a7" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_cddcaf418dc4d392ecfcca842a7"`);
+        await queryRunner.query(`ALTER TABLE "user_list_membership" DROP CONSTRAINT "FK_021015e6683570ae9f6b0c62bee"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_e4f3094c43f2d665e6030b0337"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_cddcaf418dc4d392ecfcca842a"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_021015e6683570ae9f6b0c62be"`);
+        await queryRunner.query(`COMMENT ON COLUMN "user_list_membership"."createdAt" IS 'The created date of the UserListJoining.'`);
+        await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "preservedUsernames" SET DEFAULT '{admin,administrator,root,system,maintainer,host,mod,moderator,owner,superuser,staff,auth,i,me,everyone,all,mention,mentions,example,user,users,account,accounts,official,help,helps,support,supports,info,information,informations,announce,announces,announcement,announcements,notice,notification,notifications,dev,developer,developers,tech,misskey}'`);
+        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_90f7da835e4c10aca6853621e1" ON "user_list_membership" ("userId", "userListId") `);
+        await queryRunner.query(`CREATE INDEX "IDX_605472305f26818cc93d1baaa7" ON "user_list_membership" ("userListId") `);
+        await queryRunner.query(`CREATE INDEX "IDX_d844bfc6f3f523a05189076efa" ON "user_list_membership" ("userId") `);
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_605472305f26818cc93d1baaa74" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ADD CONSTRAINT "FK_d844bfc6f3f523a05189076efaa" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+}
diff --git a/packages/backend/migration/1696373953614-meta-cache-settings.js b/packages/backend/migration/1696373953614-meta-cache-settings.js
new file mode 100644
index 0000000000000000000000000000000000000000..f994b76ef26325dff6b502282b757a38a93ac0ac
--- /dev/null
+++ b/packages/backend/migration/1696373953614-meta-cache-settings.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class MetaCacheSettings1696373953614 {
+    name = 'MetaCacheSettings1696373953614'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "perLocalUserUserTimelineCacheMax" integer NOT NULL DEFAULT '300'`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "perRemoteUserUserTimelineCacheMax" integer NOT NULL DEFAULT '100'`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "perUserHomeTimelineCacheMax" integer NOT NULL DEFAULT '300'`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "perUserListTimelineCacheMax" integer NOT NULL DEFAULT '300'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserListTimelineCacheMax"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perUserHomeTimelineCacheMax"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perRemoteUserUserTimelineCacheMax"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "perLocalUserUserTimelineCacheMax"`);
+    }
+}
diff --git a/packages/backend/migration/1696388600237-revert-note-edit.js b/packages/backend/migration/1696388600237-revert-note-edit.js
new file mode 100644
index 0000000000000000000000000000000000000000..83bc552c35a7aa91b88363cfdb3bcc23a71b5c6f
--- /dev/null
+++ b/packages/backend/migration/1696388600237-revert-note-edit.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class RevertNoteEdit1696388600237 {
+    name = 'RevertNoteEdit1696388600237'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "updatedAt"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" ADD "updatedAt" TIMESTAMP WITH TIME ZONE`);
+    }
+}
diff --git a/packages/backend/migration/1696405744672-clean-up.js b/packages/backend/migration/1696405744672-clean-up.js
new file mode 100644
index 0000000000000000000000000000000000000000..5ec89b08f4fd5325700ebcee0cb93cf0ad6c3c06
--- /dev/null
+++ b/packages/backend/migration/1696405744672-clean-up.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CleanUp1696405744672 {
+    name = 'CleanUp1696405744672'
+
+    async up(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_e7c0567f5261063592f022e9b5"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_25dfc71b0369b003a4cd434d0b"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`CREATE INDEX "IDX_25dfc71b0369b003a4cd434d0b" ON "note" ("attachedFileTypes") `);
+        await queryRunner.query(`CREATE INDEX "IDX_e7c0567f5261063592f022e9b5" ON "note" ("createdAt") `);
+    }
+}
diff --git a/packages/backend/migration/1696569742153-clean-up.js b/packages/backend/migration/1696569742153-clean-up.js
new file mode 100644
index 0000000000000000000000000000000000000000..de48fab5aa84dfb5bd7b93277e526bff65fb1b70
--- /dev/null
+++ b/packages/backend/migration/1696569742153-clean-up.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CleanUp1696569742153 {
+    name = 'CleanUp1696569742153'
+
+    async up(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_01f4581f114e0ebd2bbb876f0b"`);
+        await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "score"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "note" ADD "score" integer NOT NULL DEFAULT '0'`);
+        await queryRunner.query(`CREATE INDEX "IDX_01f4581f114e0ebd2bbb876f0b" ON "note_reaction" ("createdAt") `);
+    }
+}
diff --git a/packages/backend/migration/1696581429196-clean-up.js b/packages/backend/migration/1696581429196-clean-up.js
new file mode 100644
index 0000000000000000000000000000000000000000..da69b4e9de05f83eed88c77b6ee88160e4ef3ea0
--- /dev/null
+++ b/packages/backend/migration/1696581429196-clean-up.js
@@ -0,0 +1,15 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class CleanUp1696581429196 {
+    name = 'CleanUp1696581429196'
+
+    async up(queryRunner) {
+        await queryRunner.query(`DROP TABLE IF EXISTS "muted_note"`);
+    }
+
+    async down(queryRunner) {
+    }
+}
diff --git a/packages/backend/migration/1696743032098-AdsOnStream.js b/packages/backend/migration/1696743032098-AdsOnStream.js
new file mode 100644
index 0000000000000000000000000000000000000000..c86ee84883283081926f7fba4c6a869d046845f1
--- /dev/null
+++ b/packages/backend/migration/1696743032098-AdsOnStream.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class AdsOnStream1696743032098 {
+    name = 'AdsOnStream1696743032098'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "notesPerOneAd" integer NOT NULL DEFAULT '0'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "notesPerOneAd"`);
+    }
+}
diff --git a/packages/backend/migration/1696807733453-userListUserId.js b/packages/backend/migration/1696807733453-userListUserId.js
new file mode 100644
index 0000000000000000000000000000000000000000..ab2ba07fb5f8011d7f62e04e498b35a10c4d066b
--- /dev/null
+++ b/packages/backend/migration/1696807733453-userListUserId.js
@@ -0,0 +1,21 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class UserListUserId1696807733453 {
+    name = 'UserListUserId1696807733453'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ADD "userListUserId" character varying(32) NOT NULL DEFAULT ''`);
+				const memberships = await queryRunner.query(`SELECT "id", "userListId" FROM "user_list_membership"`);
+				for(let i = 0; i < memberships.length; i++) {
+					const userList = await queryRunner.query(`SELECT "userId" FROM "user_list" WHERE "id" = $1`, [memberships[i].userListId]);
+					await queryRunner.query(`UPDATE "user_list_membership" SET "userListUserId" = $1 WHERE "id" = $2`, [userList[0].userId, memberships[i].id]);
+				}
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_membership" DROP COLUMN "userListUserId"`);
+    }
+}
diff --git a/packages/backend/migration/1696808725134-userListUserId-2.js b/packages/backend/migration/1696808725134-userListUserId-2.js
new file mode 100644
index 0000000000000000000000000000000000000000..5bcb5aedc2fbf4196934ec0b76d75384563b9ba4
--- /dev/null
+++ b/packages/backend/migration/1696808725134-userListUserId-2.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class UserListUserId21696808725134 {
+    name = 'UserListUserId21696808725134'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" DROP DEFAULT`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_membership" ALTER COLUMN "userListUserId" SET DEFAULT ''`);
+    }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 2fbf9de8a6d988c4a8460a1bf6896c9aa56a3e67..bc24b4938cac568d953b2ccb87778838ce320f57 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -71,14 +71,14 @@
 		"@fastify/multipart": "8.0.0",
 		"@fastify/static": "6.11.2",
 		"@fastify/view": "8.2.0",
-		"@nestjs/common": "10.2.6",
-		"@nestjs/core": "10.2.6",
-		"@nestjs/testing": "10.2.6",
+		"@nestjs/common": "10.2.7",
+		"@nestjs/core": "10.2.7",
+		"@nestjs/testing": "10.2.7",
 		"@peertube/http-signature": "1.7.0",
 		"@simplewebauthn/server": "8.2.0",
 		"@sinonjs/fake-timers": "11.1.0",
 		"@swc/cli": "0.1.62",
-		"@swc/core": "1.3.90",
+		"@swc/core": "1.3.92",
 		"accepts": "1.3.8",
 		"ajv": "8.12.0",
 		"archiver": "6.0.1",
@@ -86,7 +86,7 @@
 		"bcryptjs": "2.4.3",
 		"blurhash": "2.0.5",
 		"body-parser": "1.20.2",
-		"bullmq": "4.11.4",
+		"bullmq": "4.12.3",
 		"cacheable-lookup": "7.0.0",
 		"cbor": "9.0.1",
 		"chalk": "5.3.0",
@@ -124,13 +124,13 @@
 		"nanoid": "5.0.1",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.2",
-		"nodemailer": "6.9.5",
+		"nodemailer": "6.9.6",
 		"nsfwjs": "2.4.2",
 		"oauth": "0.10.0",
 		"oauth2orize": "1.11.1",
 		"oauth2orize-pkce": "0.1.2",
 		"os-utils": "0.0.14",
-		"otpauth": "9.1.4",
+		"otpauth": "9.1.5",
 		"parse5": "7.1.2",
 		"pg": "8.11.3",
 		"pkce-challenge": "4.0.1",
@@ -155,7 +155,7 @@
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"summaly": "github:misskey-dev/summaly",
-		"systeminformation": "5.21.9",
+		"systeminformation": "5.21.11",
 		"tinycolor2": "1.6.0",
 		"tmp": "0.2.1",
 		"tsc-alias": "1.8.8",
@@ -189,13 +189,13 @@
 		"@types/jsrsasign": "10.5.9",
 		"@types/mime-types": "2.1.2",
 		"@types/ms": "0.7.32",
-		"@types/node": "20.7.1",
+		"@types/node": "20.8.4",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.11",
 		"@types/oauth": "0.9.2",
 		"@types/oauth2orize": "1.11.1",
 		"@types/oauth2orize-pkce": "0.1.0",
-		"@types/pg": "8.10.3",
+		"@types/pg": "8.10.4",
 		"@types/pug": "2.0.7",
 		"@types/punycode": "2.1.0",
 		"@types/qrcode": "1.5.2",
@@ -212,11 +212,11 @@
 		"@types/vary": "1.1.1",
 		"@types/web-push": "3.6.1",
 		"@types/ws": "8.5.6",
-		"@typescript-eslint/eslint-plugin": "6.7.3",
-		"@typescript-eslint/parser": "6.7.3",
+		"@typescript-eslint/eslint-plugin": "6.7.5",
+		"@typescript-eslint/parser": "6.7.5",
 		"aws-sdk-client-mock": "3.0.0",
 		"cross-env": "7.0.3",
-		"eslint": "8.50.0",
+		"eslint": "8.51.0",
 		"eslint-plugin-import": "2.28.1",
 		"execa": "8.0.1",
 		"jest": "29.7.0",
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 9f1ee9fcaa3f53bf80672d465c6469df0368982e..3e9d19f82598d71c9e161aff176d76e062089379 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -70,11 +70,19 @@ const $redisForSub: Provider = {
 	inject: [DI.config],
 };
 
+const $redisForTimelines: Provider = {
+	provide: DI.redisForTimelines,
+	useFactory: (config: Config) => {
+		return new Redis.Redis(config.redisForTimelines);
+	},
+	inject: [DI.config],
+};
+
 @Global()
 @Module({
 	imports: [RepositoryModule],
-	providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub],
-	exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule],
+	providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines],
+	exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, $redisForTimelines, RepositoryModule],
 })
 export class GlobalModule implements OnApplicationShutdown {
 	constructor(
@@ -82,6 +90,7 @@ export class GlobalModule implements OnApplicationShutdown {
 		@Inject(DI.redis) private redisClient: Redis.Redis,
 		@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
 		@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
+		@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
 	) {}
 
 	public async dispose(): Promise<void> {
@@ -98,6 +107,7 @@ export class GlobalModule implements OnApplicationShutdown {
 			this.redisClient.disconnect(),
 			this.redisForPub.disconnect(),
 			this.redisForSub.disconnect(),
+			this.redisForTimelines.disconnect(),
 		]);
 	}
 
diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts
index 4783a2b2dae593df7162463f3d1a942d4c0a9ac9..df10ab1e3db416d334e803fab1bdcbb6a771c262 100644
--- a/packages/backend/src/boot/common.ts
+++ b/packages/backend/src/boot/common.ts
@@ -17,7 +17,6 @@ export async function server() {
 	const app = await NestFactory.createApplicationContext(MainModule, {
 		logger: new NestLogger(),
 	});
-	app.enableShutdownHooks();
 
 	const serverService = app.get(ServerService);
 	await serverService.launch();
@@ -35,7 +34,6 @@ export async function jobQueue() {
 	const jobQueue = await NestFactory.createApplicationContext(QueueProcessorModule, {
 		logger: new NestLogger(),
 	});
-	jobQueue.enableShutdownHooks();
 
 	jobQueue.get(QueueProcessorService).start();
 	jobQueue.get(ChartManagementService).start();
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index f89879d535192a035ca13bba135f598e1bb552a4..ef59a80950fb9da87c9db220391afa94138755a0 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -47,6 +47,7 @@ type Source = {
 	redis: RedisOptionsSource;
 	redisForPubsub?: RedisOptionsSource;
 	redisForJobQueue?: RedisOptionsSource;
+	redisForTimelines?: RedisOptionsSource;
 	meilisearch?: {
 		host: string;
 		port: string;
@@ -161,6 +162,7 @@ export type Config = {
 	redis: RedisOptions & RedisOptionsSource;
 	redisForPubsub: RedisOptions & RedisOptionsSource;
 	redisForJobQueue: RedisOptions & RedisOptionsSource;
+	redisForTimelines: RedisOptions & RedisOptionsSource;
 	perChannelMaxNoteCacheCount: number;
 	perUserNotificationsMaxCount: number;
 	deactivateAntennaThreshold: number;
@@ -227,6 +229,7 @@ export function loadConfig(): Config {
 		redis,
 		redisForPubsub: config.redisForPubsub ? convertRedisOptions(config.redisForPubsub, host) : redis,
 		redisForJobQueue: config.redisForJobQueue ? convertRedisOptions(config.redisForJobQueue, host) : redis,
+		redisForTimelines: config.redisForTimelines ? convertRedisOptions(config.redisForTimelines, host) : redis,
 		id: config.id,
 		proxy: config.proxy,
 		proxySmtp: config.proxySmtp,
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index ec1d013922b78d1b64857fe8affe045e1167aaec..db64f42754cc7713e8d8245a3d90e95463231f3e 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -9,7 +9,7 @@ import { IsNull, In, MoreThan, Not } from 'typeorm';
 import { bindThis } from '@/decorators.js';
 import { DI } from '@/di-symbols.js';
 import type { MiLocalUser, MiRemoteUser, MiUser } from '@/models/User.js';
-import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/_.js';
+import type { BlockingsRepository, FollowingsRepository, InstancesRepository, MutingsRepository, UserListMembershipsRepository, UsersRepository } from '@/models/_.js';
 import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
 
 import { IdService } from '@/core/IdService.js';
@@ -42,8 +42,8 @@ export class AccountMoveService {
 		@Inject(DI.mutingsRepository)
 		private mutingsRepository: MutingsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		@Inject(DI.instancesRepository)
 		private instancesRepository: InstancesRepository,
@@ -215,40 +215,41 @@ export class AccountMoveService {
 	@bindThis
 	public async updateLists(src: ThinUser, dst: MiUser): Promise<void> {
 		// Return if there is no list to be updated.
-		const oldJoinings = await this.userListJoiningsRepository.find({
+		const oldMemberships = await this.userListMembershipsRepository.find({
 			where: {
 				userId: src.id,
 			},
 		});
-		if (oldJoinings.length === 0) return;
+		if (oldMemberships.length === 0) return;
 
-		const existingUserListIds = await this.userListJoiningsRepository.find({
+		const existingUserListIds = await this.userListMembershipsRepository.find({
 			where: {
 				userId: dst.id,
 			},
-		}).then(joinings => joinings.map(joining => joining.userListId));
+		}).then(memberships => memberships.map(membership => membership.userListId));
 
-		const newJoinings: Map<string, { createdAt: Date; userId: string; userListId: string; }> = new Map();
+		const newMemberships: Map<string, { createdAt: Date; userId: string; userListId: string; userListUserId: string; }> = new Map();
 
 		// 重複しないようにIDを生成
 		const genId = (): string => {
 			let id: string;
 			do {
 				id = this.idService.genId();
-			} while (newJoinings.has(id));
+			} while (newMemberships.has(id));
 			return id;
 		};
-		for (const joining of oldJoinings) {
-			if (existingUserListIds.includes(joining.userListId)) continue; // skip if dst exists in this user's list
-			newJoinings.set(genId(), {
+		for (const membership of oldMemberships) {
+			if (existingUserListIds.includes(membership.userListId)) continue; // skip if dst exists in this user's list
+			newMemberships.set(genId(), {
 				createdAt: new Date(),
 				userId: dst.id,
-				userListId: joining.userListId,
+				userListId: membership.userListId,
+				userListUserId: membership.userListUserId,
 			});
 		}
 
-		const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
-		await this.userListJoiningsRepository.insert(arrayToInsert);
+		const arrayToInsert = Array.from(newMemberships.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
+		await this.userListMembershipsRepository.insert(arrayToInsert);
 
 		// Have the proxy account follow the new account in the same way as UserListService.push
 		if (this.userEntityService.isRemoteUser(dst)) {
diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts
index ddacc0936f1c270a560cc92bad183804a0e7a1d7..a5330db53f5950ed34270d801f5565bd6793fea0 100644
--- a/packages/backend/src/core/AnnouncementService.ts
+++ b/packages/backend/src/core/AnnouncementService.ts
@@ -158,9 +158,13 @@ export class AnnouncementService {
 
 		if (moderator) {
 			if (announcement.userId) {
+				const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
 				this.moderationLogService.log(moderator, 'deleteUserAnnouncement', {
 					announcementId: announcement.id,
 					announcement: announcement,
+					userId: announcement.userId,
+					userUsername: user.username,
+					userHost: user.host,
 				});
 			} else {
 				this.moderationLogService.log(moderator, 'deleteGlobalAnnouncement', {
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index d9f27b8c63061981f4b6e7a9b7acfe396030abb9..ca7624b1d4fbeb18089441f1e230229d4bda5af0 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -12,10 +12,11 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import * as Acct from '@/misc/acct.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, UserListJoiningsRepository } from '@/models/_.js';
+import type { AntennasRepository, UserListMembershipsRepository } from '@/models/_.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import type { GlobalEvents } from '@/core/GlobalEventService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
 
 @Injectable()
@@ -24,8 +25,8 @@ export class AntennaService implements OnApplicationShutdown {
 	private antennas: MiAntenna[];
 
 	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
 
 		@Inject(DI.redisForSub)
 		private redisForSub: Redis.Redis,
@@ -33,11 +34,12 @@ export class AntennaService implements OnApplicationShutdown {
 		@Inject(DI.antennasRepository)
 		private antennasRepository: AntennasRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private utilityService: UtilityService,
 		private globalEventService: GlobalEventService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		this.antennasFetched = false;
 		this.antennas = [];
@@ -81,15 +83,10 @@ export class AntennaService implements OnApplicationShutdown {
 		const antennasWithMatchResult = await Promise.all(antennas.map(antenna => this.checkHitAntenna(antenna, note, noteUser).then(hit => [antenna, hit] as const)));
 		const matchedAntennas = antennasWithMatchResult.filter(([, hit]) => hit).map(([antenna]) => antenna);
 
-		const redisPipeline = this.redisClient.pipeline();
+		const redisPipeline = this.redisForTimelines.pipeline();
 
 		for (const antenna of matchedAntennas) {
-			redisPipeline.xadd(
-				`antennaTimeline:${antenna.id}`,
-				'MAXLEN', '~', '200',
-				'*',
-				'note', note.id);
-
+			this.redisTimelineService.push(`antennaTimeline:${antenna.id}`, note.id, 200, redisPipeline);
 			this.globalEventService.publishAntennaStream(antenna.id, 'note', note);
 		}
 
@@ -108,7 +105,7 @@ export class AntennaService implements OnApplicationShutdown {
 		if (antenna.src === 'home') {
 			// TODO
 		} else if (antenna.src === 'list') {
-			const listUsers = (await this.userListJoiningsRepository.findBy({
+			const listUsers = (await this.userListMembershipsRepository.findBy({
 				userListId: antenna.userListId!,
 			})).map(x => x.userId);
 
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index 561979c4bf142f85cd09f316f2e53d4cbd4b5ff7..22c510cc378d3fe692c27ca43df6eaedecc418e9 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -5,7 +5,7 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, MiUserProfile, UserProfilesRepository, UsersRepository, MiFollowing } from '@/models/_.js';
 import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
 import type { MiLocalUser, MiUser } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
@@ -25,7 +25,7 @@ export class CacheService implements OnApplicationShutdown {
 	public userBlockingCache: RedisKVCache<Set<string>>;
 	public userBlockedCache: RedisKVCache<Set<string>>; // NOTE: 「被」Blockキャッシュ
 	public renoteMutingsCache: RedisKVCache<Set<string>>;
-	public userFollowingsCache: RedisKVCache<Set<string>>;
+	public userFollowingsCache: RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>;
 	public userFollowingChannelsCache: RedisKVCache<Set<string>>;
 
 	constructor(
@@ -136,12 +136,18 @@ export class CacheService implements OnApplicationShutdown {
 			fromRedisConverter: (value) => new Set(JSON.parse(value)),
 		});
 
-		this.userFollowingsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userFollowings', {
+		this.userFollowingsCache = new RedisKVCache<Record<string, Pick<MiFollowing, 'withReplies'> | undefined>>(this.redisClient, 'userFollowings', {
 			lifetime: 1000 * 60 * 30, // 30m
 			memoryCacheLifetime: 1000 * 60, // 1m
-			fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId'] }).then(xs => new Set(xs.map(x => x.followeeId))),
-			toRedisConverter: (value) => JSON.stringify(Array.from(value)),
-			fromRedisConverter: (value) => new Set(JSON.parse(value)),
+			fetcher: (key) => this.followingsRepository.find({ where: { followerId: key }, select: ['followeeId', 'withReplies'] }).then(xs => {
+				const obj: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
+				for (const x of xs) {
+					obj[x.followeeId] = { withReplies: x.withReplies };
+				}
+				return obj;
+			}),
+			toRedisConverter: (value) => JSON.stringify(value),
+			fromRedisConverter: (value) => JSON.parse(value),
 		});
 
 		this.userFollowingChannelsCache = new RedisKVCache<Set<string>>(this.redisClient, 'userFollowingChannels', {
@@ -188,6 +194,7 @@ export class CacheService implements OnApplicationShutdown {
 					if (follower) follower.followingCount++;
 					const followee = this.userByIdCache.get(body.followeeId);
 					if (followee) followee.followersCount++;
+					this.userFollowingsCache.delete(body.followerId);
 					break;
 				}
 				default:
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 78333e70a5e0f90fe3e44fadcbec9777f8079120..0dc025d998cea05db453e701da898067d3a03de7 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -46,6 +46,7 @@ import { SignupService } from './SignupService.js';
 import { WebAuthnService } from './WebAuthnService.js';
 import { UserBlockingService } from './UserBlockingService.js';
 import { CacheService } from './CacheService.js';
+import { UserService } from './UserService.js';
 import { UserFollowingService } from './UserFollowingService.js';
 import { UserKeypairService } from './UserKeypairService.js';
 import { UserListService } from './UserListService.js';
@@ -59,6 +60,8 @@ import { UtilityService } from './UtilityService.js';
 import { FileInfoService } from './FileInfoService.js';
 import { SearchService } from './SearchService.js';
 import { ClipService } from './ClipService.js';
+import { FeaturedService } from './FeaturedService.js';
+import { RedisTimelineService } from './RedisTimelineService.js';
 import { ChartLoggerService } from './chart/ChartLoggerService.js';
 import FederationChart from './chart/charts/federation.js';
 import NotesChart from './chart/charts/notes.js';
@@ -173,6 +176,7 @@ const $SignupService: Provider = { provide: 'SignupService', useExisting: Signup
 const $WebAuthnService: Provider = { provide: 'WebAuthnService', useExisting: WebAuthnService };
 const $UserBlockingService: Provider = { provide: 'UserBlockingService', useExisting: UserBlockingService };
 const $CacheService: Provider = { provide: 'CacheService', useExisting: CacheService };
+const $UserService: Provider = { provide: 'UserService', useExisting: UserService };
 const $UserFollowingService: Provider = { provide: 'UserFollowingService', useExisting: UserFollowingService };
 const $UserKeypairService: Provider = { provide: 'UserKeypairService', useExisting: UserKeypairService };
 const $UserListService: Provider = { provide: 'UserListService', useExisting: UserListService };
@@ -185,6 +189,8 @@ const $UtilityService: Provider = { provide: 'UtilityService', useExisting: Util
 const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
 const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
 const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService };
+const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
+const $RedisTimelineService: Provider = { provide: 'RedisTimelineService', useExisting: RedisTimelineService };
 
 const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
 const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -303,6 +309,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		WebAuthnService,
 		UserBlockingService,
 		CacheService,
+		UserService,
 		UserFollowingService,
 		UserKeypairService,
 		UserListService,
@@ -315,6 +322,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FileInfoService,
 		SearchService,
 		ClipService,
+		FeaturedService,
+		RedisTimelineService,
 		ChartLoggerService,
 		FederationChart,
 		NotesChart,
@@ -426,6 +435,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$WebAuthnService,
 		$UserBlockingService,
 		$CacheService,
+		$UserService,
 		$UserFollowingService,
 		$UserKeypairService,
 		$UserListService,
@@ -438,6 +448,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FileInfoService,
 		$SearchService,
 		$ClipService,
+		$FeaturedService,
+		$RedisTimelineService,
 		$ChartLoggerService,
 		$FederationChart,
 		$NotesChart,
@@ -550,6 +562,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		WebAuthnService,
 		UserBlockingService,
 		CacheService,
+		UserService,
 		UserFollowingService,
 		UserKeypairService,
 		UserListService,
@@ -562,6 +575,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FileInfoService,
 		SearchService,
 		ClipService,
+		FeaturedService,
+		RedisTimelineService,
 		FederationChart,
 		NotesChart,
 		UsersChart,
@@ -672,6 +687,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$WebAuthnService,
 		$UserBlockingService,
 		$CacheService,
+		$UserService,
 		$UserFollowingService,
 		$UserKeypairService,
 		$UserListService,
@@ -684,6 +700,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FileInfoService,
 		$SearchService,
 		$ClipService,
+		$FeaturedService,
+		$RedisTimelineService,
 		$FederationChart,
 		$NotesChart,
 		$UsersChart,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 9661a0aea3327c902c0847daf77143cc95f83a90..145c224f678af655094454d62a3c4485b1792b08 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -48,7 +48,6 @@ export class CustomEmojiService implements OnApplicationShutdown {
 			fetcher: () => this.emojisRepository.find({ where: { host: IsNull() } }).then(emojis => new Map(emojis.map(emoji => [emoji.name, emoji]))),
 			toRedisConverter: (value) => JSON.stringify(Array.from(value.values())),
 			fromRedisConverter: (value) => {
-				if (!Array.isArray(JSON.parse(value))) return undefined; // 古いバージョンの壊れたキャッシュが残っていることがある(そのうち消す)
 				return new Map(JSON.parse(value).map((x: Serialized<MiEmoji>) => [x.name, {
 					...x,
 					updatedAt: x.updatedAt ? new Date(x.updatedAt) : null,
@@ -380,6 +379,20 @@ export class CustomEmojiService implements OnApplicationShutdown {
 		}
 	}
 
+	/**
+	 * ローカル内の絵文字に重複がないかチェックします
+	 * @param name 絵文字名
+	 */
+	@bindThis
+	public checkDuplicate(name: string): Promise<boolean> {
+		return this.emojisRepository.exist({ where: { name, host: IsNull() } });
+	}
+
+	@bindThis
+	public getEmojiById(id: string): Promise<MiEmoji | null> {
+		return this.emojisRepository.findOneBy({ id });
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.cache.dispose();
diff --git a/packages/backend/src/core/FeaturedService.ts b/packages/backend/src/core/FeaturedService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..cccbbd95cb86fce97f7781d6524ff9cf3e25a366
--- /dev/null
+++ b/packages/backend/src/core/FeaturedService.ts
@@ -0,0 +1,116 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import type { MiNote, MiUser } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+
+const GLOBAL_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 3; // 3日ごと
+const PER_USER_NOTES_RANKING_WINDOW = 1000 * 60 * 60 * 24 * 7; // 1週間ごと
+const HASHTAG_RANKING_WINDOW = 1000 * 60 * 60; // 1時間ごと
+
+@Injectable()
+export class FeaturedService {
+	constructor(
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
+	) {
+	}
+
+	@bindThis
+	private getCurrentWindow(windowRange: number): number {
+		const passed = new Date().getTime() - new Date(new Date().getFullYear(), 0, 1).getTime();
+		return Math.floor(passed / windowRange);
+	}
+
+	@bindThis
+	private async updateRankingOf(name: string, windowRange: number, element: string, score = 1): Promise<void> {
+		const currentWindow = this.getCurrentWindow(windowRange);
+		const redisTransaction = this.redisClient.multi();
+		redisTransaction.zincrby(
+			`${name}:${currentWindow}`,
+			score,
+			element);
+		redisTransaction.expire(
+			`${name}:${currentWindow}`,
+			(windowRange * 3) / 1000,
+			'NX'); // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
+		await redisTransaction.exec();
+	}
+
+	@bindThis
+	private async getRankingOf(name: string, windowRange: number, threshold: number): Promise<string[]> {
+		const currentWindow = this.getCurrentWindow(windowRange);
+		const previousWindow = currentWindow - 1;
+
+		const redisPipeline = this.redisClient.pipeline();
+		redisPipeline.zrange(
+			`${name}:${currentWindow}`, 0, threshold, 'REV', 'WITHSCORES');
+		redisPipeline.zrange(
+			`${name}:${previousWindow}`, 0, threshold, 'REV', 'WITHSCORES');
+		const [currentRankingResult, previousRankingResult] = await redisPipeline.exec().then(result => result ? result.map(r => r[1] as string[]) : [[], []]);
+
+		const ranking = new Map<string, number>();
+		for (let i = 0; i < currentRankingResult.length; i += 2) {
+			const noteId = currentRankingResult[i];
+			const score = parseInt(currentRankingResult[i + 1], 10);
+			ranking.set(noteId, score);
+		}
+		for (let i = 0; i < previousRankingResult.length; i += 2) {
+			const noteId = previousRankingResult[i];
+			const score = parseInt(previousRankingResult[i + 1], 10);
+			const exist = ranking.get(noteId);
+			if (exist != null) {
+				ranking.set(noteId, (exist + score) / 2);
+			} else {
+				ranking.set(noteId, score);
+			}
+		}
+
+		return Array.from(ranking.keys());
+	}
+
+	@bindThis
+	public updateGlobalNotesRanking(noteId: MiNote['id'], score = 1): Promise<void> {
+		return this.updateRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
+	}
+
+	@bindThis
+	public updateInChannelNotesRanking(channelId: MiNote['channelId'], noteId: MiNote['id'], score = 1): Promise<void> {
+		return this.updateRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, noteId, score);
+	}
+
+	@bindThis
+	public updatePerUserNotesRanking(userId: MiUser['id'], noteId: MiNote['id'], score = 1): Promise<void> {
+		return this.updateRankingOf(`featuredPerUserNotesRanking:${userId}`, PER_USER_NOTES_RANKING_WINDOW, noteId, score);
+	}
+
+	@bindThis
+	public updateHashtagsRanking(hashtag: string, score = 1): Promise<void> {
+		return this.updateRankingOf('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, hashtag, score);
+	}
+
+	@bindThis
+	public getGlobalNotesRanking(threshold: number): Promise<MiNote['id'][]> {
+		return this.getRankingOf('featuredGlobalNotesRanking', GLOBAL_NOTES_RANKING_WINDOW, threshold);
+	}
+
+	@bindThis
+	public getInChannelNotesRanking(channelId: MiNote['channelId'], threshold: number): Promise<MiNote['id'][]> {
+		return this.getRankingOf(`featuredInChannelNotesRanking:${channelId}`, GLOBAL_NOTES_RANKING_WINDOW, threshold);
+	}
+
+	@bindThis
+	public getPerUserNotesRanking(userId: MiUser['id'], threshold: number): Promise<MiNote['id'][]> {
+		return this.getRankingOf(`featuredPerUserNotesRanking:${userId}`, PER_USER_NOTES_RANKING_WINDOW, threshold);
+	}
+
+	@bindThis
+	public getHashtagsRanking(threshold: number): Promise<string[]> {
+		return this.getRankingOf('featuredHashtagsRanking', HASHTAG_RANKING_WINDOW, threshold);
+	}
+}
diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index c72c7460ff21bb00264e77cb4c37de49e6b7516a..ddff28359a08db6db5797d5f69f7e90f5ec05dbf 100644
--- a/packages/backend/src/core/HashtagService.ts
+++ b/packages/backend/src/core/HashtagService.ts
@@ -4,6 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import type { MiUser } from '@/models/User.js';
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
@@ -12,15 +13,22 @@ import type { MiHashtag } from '@/models/Hashtag.js';
 import type { HashtagsRepository } from '@/models/_.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
+import { MetaService } from '@/core/MetaService.js';
 
 @Injectable()
 export class HashtagService {
 	constructor(
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis, // TODO: 専用のRedisサーバーを設定できるようにする
+
 		@Inject(DI.hashtagsRepository)
 		private hashtagsRepository: HashtagsRepository,
 
 		private userEntityService: UserEntityService,
+		private featuredService: FeaturedService,
 		private idService: IdService,
+		private metaService: MetaService,
 	) {
 	}
 
@@ -46,6 +54,9 @@ export class HashtagService {
 	public async updateHashtag(user: { id: MiUser['id']; host: MiUser['host']; }, tag: string, isUserAttached = false, inc = true) {
 		tag = normalizeForSearch(tag);
 
+		// TODO: サンプリング
+		this.updateHashtagsRanking(tag, user.id);
+
 		const index = await this.hashtagsRepository.findOneBy({ name: tag });
 
 		if (index == null && !inc) return;
@@ -85,7 +96,7 @@ export class HashtagService {
 					}
 				}
 			} else {
-			// 自分が初めてこのタグを使ったなら
+				// 自分が初めてこのタグを使ったなら
 				if (!index.mentionedUserIds.some(id => id === user.id)) {
 					set.mentionedUserIds = () => `array_append("mentionedUserIds", '${user.id}')`;
 					set.mentionedUsersCount = () => '"mentionedUsersCount" + 1';
@@ -144,4 +155,94 @@ export class HashtagService {
 			}
 		}
 	}
+
+	@bindThis
+	public async updateHashtagsRanking(hashtag: string, userId: MiUser['id']): Promise<void> {
+		const instance = await this.metaService.fetch();
+		const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
+		if (hiddenTags.includes(hashtag)) return;
+
+		// YYYYMMDDHHmm (10分間隔)
+		const now = new Date();
+		now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0);
+		const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`;
+
+		const exist = await this.redisClient.sismember(`hashtagUsers:${hashtag}`, userId);
+		if (exist === 1) return;
+
+		this.featuredService.updateHashtagsRanking(hashtag, 1);
+
+		const redisPipeline = this.redisClient.pipeline();
+
+		// チャート用
+		redisPipeline.pfadd(`hashtagUsers:${hashtag}:${window}`, userId);
+		redisPipeline.expire(`hashtagUsers:${hashtag}:${window}`,
+			60 * 60 * 24 * 3, // 3日間
+			'NX', // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
+		);
+
+		// ユニークカウント用
+		// TODO: Bloom Filter を使うようにしても良さそう
+		redisPipeline.sadd(`hashtagUsers:${hashtag}`, userId);
+		redisPipeline.expire(`hashtagUsers:${hashtag}`,
+			60 * 60, // 1時間
+			'NX', // "NX -- Set expiry only when the key has no expiry" = 有効期限がないときだけ設定
+		);
+
+		redisPipeline.exec();
+	}
+
+	@bindThis
+	public async getChart(hashtag: string, range: number): Promise<number[]> {
+		const now = new Date();
+		now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0);
+
+		const redisPipeline = this.redisClient.pipeline();
+
+		for (let i = 0; i < range; i++) {
+			const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`;
+			redisPipeline.pfcount(`hashtagUsers:${hashtag}:${window}`);
+			now.setMinutes(now.getMinutes() - (i * 10), 0, 0);
+		}
+
+		const result = await redisPipeline.exec();
+
+		if (result == null) return [];
+
+		return result.map(x => x[1]) as number[];
+	}
+
+	@bindThis
+	public async getCharts(hashtags: string[], range: number): Promise<Record<string, number[]>> {
+		const now = new Date();
+		now.setMinutes(Math.floor(now.getMinutes() / 10) * 10, 0, 0);
+
+		const redisPipeline = this.redisClient.pipeline();
+
+		for (let i = 0; i < range; i++) {
+			const window = `${now.getUTCFullYear()}${(now.getUTCMonth() + 1).toString().padStart(2, '0')}${now.getUTCDate().toString().padStart(2, '0')}${now.getUTCHours().toString().padStart(2, '0')}${now.getUTCMinutes().toString().padStart(2, '0')}`;
+			for (const hashtag of hashtags) {
+				redisPipeline.pfcount(`hashtagUsers:${hashtag}:${window}`);
+			}
+			now.setMinutes(now.getMinutes() - (i * 10), 0, 0);
+		}
+
+		const result = await redisPipeline.exec();
+
+		if (result == null) return {};
+
+		// key is hashtag
+		const charts = {} as Record<string, number[]>;
+		for (const hashtag of hashtags) {
+			charts[hashtag] = [];
+		}
+
+		for (let i = 0; i < range; i++) {
+			for (let j = 0; j < hashtags.length; j++) {
+				charts[hashtags[j]].push(result[(i * hashtags.length) + j][1] as number);
+			}
+		}
+
+		return charts;
+	}
 }
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index f20727ce418c90dbc5c14b2a90ba11cf6c5273a1..2a734671228a278501c28075b31c3f97b3e4c5b2 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -5,7 +5,7 @@
 
 import { setImmediate } from 'node:timers/promises';
 import * as mfm from 'mfm-js';
-import { In, DataSource } from 'typeorm';
+import { In, DataSource, IsNull, LessThan } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import RE2 from 're2';
@@ -14,7 +14,7 @@ import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mf
 import { extractHashtags } from '@/misc/extract-hashtags.js';
 import type { IMentionedRemoteUsers } from '@/models/Note.js';
 import { MiNote } from '@/models/Note.js';
-import type { ChannelsRepository, FollowingsRepository, InstancesRepository, MutedNotesRepository, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { ChannelFollowingsRepository, ChannelsRepository, FollowingsRepository, InstancesRepository, MiFollowing, MutingsRepository, NotesRepository, NoteThreadMutingsRepository, UserListMembershipsRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import type { MiApp } from '@/models/App.js';
 import { concat } from '@/misc/prelude/array.js';
@@ -53,8 +53,8 @@ import { DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { RoleService } from '@/core/RoleService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
-
-const mutedWordsCache = new MemorySingleCache<{ userId: MiUserProfile['userId']; mutedWords: MiUserProfile['mutedWords']; }[]>(1000 * 60 * 5);
+import { FeaturedService } from '@/core/FeaturedService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -157,8 +157,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 		@Inject(DI.db)
 		private db: DataSource,
 
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
 
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
@@ -175,8 +175,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 		@Inject(DI.userProfilesRepository)
 		private userProfilesRepository: UserProfilesRepository,
 
-		@Inject(DI.mutedNotesRepository)
-		private mutedNotesRepository: MutedNotesRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		@Inject(DI.channelsRepository)
 		private channelsRepository: ChannelsRepository,
@@ -187,11 +187,15 @@ export class NoteCreateService implements OnApplicationShutdown {
 		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
+		@Inject(DI.channelFollowingsRepository)
+		private channelFollowingsRepository: ChannelFollowingsRepository,
+
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
 		private idService: IdService,
 		private globalEventService: GlobalEventService,
 		private queueService: QueueService,
+		private redisTimelineService: RedisTimelineService,
 		private noteReadService: NoteReadService,
 		private notificationService: NotificationService,
 		private relayService: RelayService,
@@ -199,6 +203,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private hashtagService: HashtagService,
 		private antennaService: AntennaService,
 		private webhookService: WebhookService,
+		private featuredService: FeaturedService,
 		private remoteUserResolveService: RemoteUserResolveService,
 		private apDeliverManagerService: ApDeliverManagerService,
 		private apRendererService: ApRendererService,
@@ -251,19 +256,30 @@ export class NoteCreateService implements OnApplicationShutdown {
 			}
 		}
 
-		// Renote対象が「ホームまたは全体」以外の公開範囲ならreject
-		if (data.renote && data.renote.visibility !== 'public' && data.renote.visibility !== 'home' && data.renote.userId !== user.id) {
-			throw new Error('Renote target is not public or home');
-		}
-
-		// Renote対象がpublicではないならhomeにする
-		if (data.renote && data.renote.visibility !== 'public' && data.visibility === 'public') {
-			data.visibility = 'home';
-		}
+		if (data.renote) {
+			switch (data.renote.visibility) {
+				case 'public':
+					// public noteは無条件にrenote可能
+					break;
+				case 'home':
+					// home noteはhome以下にrenote可能
+					if (data.visibility === 'public') {
+						data.visibility = 'home';
+					}
+					break;
+				case 'followers':
+					// 他人のfollowers noteはreject
+					if (data.renote.userId !== user.id) {
+						throw new Error('Renote target is not public or home');
+					}
 
-		// Renote対象がfollowersならfollowersにする
-		if (data.renote && data.renote.visibility === 'followers') {
-			data.visibility = 'followers';
+					// Renote対象がfollowersならfollowersにする
+					data.visibility = 'followers';
+					break;
+				case 'specified':
+					// specified / direct noteはreject
+					throw new Error('Renote target is not public or home');
+			}
 		}
 
 		// 返信対象がpublicではないならhomeにする
@@ -333,14 +349,6 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
 
-		if (data.channel) {
-			this.redisClient.xadd(
-				`channelTimeline:${data.channel.id}`,
-				'MAXLEN', '~', this.config.perChannelMaxNoteCacheCount.toString(),
-				'*',
-				'note', note.id);
-		}
-
 		setImmediate('post created', { signal: this.#shutdownController.signal }).then(
 			() => this.postNoteCreated(note, user, data, silent, tags!, mentionedUsers!),
 			() => { /* aborted, ignore this */ },
@@ -480,26 +488,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		// Increment notes count (user)
 		this.incNotesCountOfUser(user);
 
-		// Word mute
-		mutedWordsCache.fetch(() => this.userProfilesRepository.find({
-			where: {
-				enableWordMute: true,
-			},
-			select: ['userId', 'mutedWords'],
-		})).then(us => {
-			for (const u of us) {
-				checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
-					if (shouldMute) {
-						this.mutedNotesRepository.insert({
-							id: this.idService.genId(),
-							userId: u.userId,
-							noteId: note.id,
-							reason: 'word',
-						});
-					}
-				});
-			}
-		});
+		this.pushToTl(note, user);
 
 		this.antennaService.addNoteToAntennas(note, user);
 
@@ -508,11 +497,13 @@ export class NoteCreateService implements OnApplicationShutdown {
 		}
 
 		if (data.reply == null) {
+			// TODO: キャッシュ
 			this.followingsRepository.findBy({
 				followeeId: user.id,
 				notify: 'normal',
 			}).then(followings => {
 				for (const following of followings) {
+					// TODO: ワードミュート考慮
 					this.notificationService.createNotification(following.followerId, 'note', {
 						noteId: note.id,
 					}, user.id);
@@ -520,9 +511,8 @@ export class NoteCreateService implements OnApplicationShutdown {
 			});
 		}
 
-		// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
-		if (data.renote && (await this.noteEntityService.countSameRenotes(user.id, data.renote.id, note.id) === 0)) {
-			if (!user.isBot) this.incRenoteCount(data.renote);
+		if (data.renote && data.renote.userId !== user.id && !user.isBot) {
+			this.incRenoteCount(data.renote);
 		}
 
 		if (data.poll && data.poll.expiresAt) {
@@ -722,10 +712,23 @@ export class NoteCreateService implements OnApplicationShutdown {
 		this.notesRepository.createQueryBuilder().update()
 			.set({
 				renoteCount: () => '"renoteCount" + 1',
-				score: () => '"score" + 1',
 			})
 			.where('id = :id', { id: renote.id })
 			.execute();
+
+		// 30%の確率、3日以内に投稿されたノートの場合ハイライト用ランキング更新
+		if (Math.random() < 0.3 && (Date.now() - this.idService.parse(renote.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3) {
+			if (renote.channelId != null) {
+				if (renote.replyId == null) {
+					this.featuredService.updateInChannelNotesRanking(renote.channelId, renote.id, 5);
+				}
+			} else {
+				if (renote.visibility === 'public' && renote.userHost == null && renote.replyId == null) {
+					this.featuredService.updateGlobalNotesRanking(renote.id, 5);
+					this.featuredService.updatePerUserNotesRanking(renote.userId, renote.id, 5);
+				}
+			}
+		}
 	}
 
 	@bindThis
@@ -811,6 +814,161 @@ export class NoteCreateService implements OnApplicationShutdown {
 		return mentionedUsers;
 	}
 
+	@bindThis
+	private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
+		const meta = await this.metaService.fetch();
+
+		const r = this.redisForTimelines.pipeline();
+
+		if (note.channelId) {
+			this.redisTimelineService.push(`channelTimeline:${note.channelId}`, note.id, this.config.perChannelMaxNoteCacheCount, r);
+
+			this.redisTimelineService.push(`userTimelineWithChannel:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+
+			const channelFollowings = await this.channelFollowingsRepository.find({
+				where: {
+					followeeId: note.channelId,
+				},
+				select: ['followerId'],
+			});
+
+			for (const channelFollowing of channelFollowings) {
+				this.redisTimelineService.push(`homeTimeline:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+				if (note.fileIds.length > 0) {
+					this.redisTimelineService.push(`homeTimelineWithFiles:${channelFollowing.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+				}
+			}
+		} else {
+			// TODO: キャッシュ?
+			// eslint-disable-next-line prefer-const
+			let [followings, userListMemberships] = await Promise.all([
+				this.followingsRepository.find({
+					where: {
+						followeeId: user.id,
+						followerHost: IsNull(),
+						isFollowerHibernated: false,
+					},
+					select: ['followerId', 'withReplies'],
+				}),
+				this.userListMembershipsRepository.find({
+					where: {
+						userId: user.id,
+					},
+					select: ['userListId', 'userListUserId', 'withReplies'],
+				}),
+			]);
+
+			if (note.visibility === 'followers') {
+				// TODO: 重そうだから何とかしたい Set 使う?
+				userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId));
+			}
+
+			// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
+			for (const following of followings) {
+				// 基本的にvisibleUserIdsには自身のidが含まれている前提であること
+				if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
+
+				// 自分自身以外への返信
+				if (note.replyId && note.replyUserId !== note.userId) {
+					if (!following.withReplies) continue;
+				}
+
+				this.redisTimelineService.push(`homeTimeline:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+				if (note.fileIds.length > 0) {
+					this.redisTimelineService.push(`homeTimelineWithFiles:${following.followerId}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+				}
+			}
+
+			for (const userListMembership of userListMemberships) {
+				// ダイレクトのとき、そのリストが対象外のユーザーの場合
+				if (
+					note.visibility === 'specified' &&
+					!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
+				) continue;
+
+				// 自分自身以外への返信
+				if (note.replyId && note.replyUserId !== note.userId) {
+					if (!userListMembership.withReplies) continue;
+				}
+
+				this.redisTimelineService.push(`userListTimeline:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax, r);
+				if (note.fileIds.length > 0) {
+					this.redisTimelineService.push(`userListTimelineWithFiles:${userListMembership.userListId}`, note.id, meta.perUserListTimelineCacheMax / 2, r);
+				}
+			}
+
+			if (note.visibility !== 'specified' || !note.visibleUserIds.some(v => v === user.id)) { // 自分自身のHTL
+				this.redisTimelineService.push(`homeTimeline:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax, r);
+				if (note.fileIds.length > 0) {
+					this.redisTimelineService.push(`homeTimelineWithFiles:${user.id}`, note.id, meta.perUserHomeTimelineCacheMax / 2, r);
+				}
+			}
+
+			// 自分自身以外への返信
+			if (note.replyId && note.replyUserId !== note.userId) {
+				this.redisTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+			} else {
+				this.redisTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
+				if (note.fileIds.length > 0) {
+					this.redisTimelineService.push(`userTimelineWithFiles:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax / 2 : meta.perRemoteUserUserTimelineCacheMax / 2, r);
+				}
+
+				if (note.visibility === 'public' && note.userHost == null) {
+					this.redisTimelineService.push('localTimeline', note.id, 1000, r);
+					if (note.fileIds.length > 0) {
+						this.redisTimelineService.push('localTimelineWithFiles', note.id, 500, r);
+					}
+				}
+			}
+
+			if (Math.random() < 0.1) {
+				process.nextTick(() => {
+					this.checkHibernation(followings);
+				});
+			}
+		}
+
+		r.exec();
+	}
+
+	@bindThis
+	public async checkHibernation(followings: MiFollowing[]) {
+		if (followings.length === 0) return;
+
+		const shuffle = (array: MiFollowing[]) => {
+			for (let i = array.length - 1; i > 0; i--) {
+				const j = Math.floor(Math.random() * (i + 1));
+				[array[i], array[j]] = [array[j], array[i]];
+			}
+			return array;
+		};
+
+		// ランダムに最大1000件サンプリング
+		const samples = shuffle(followings).slice(0, Math.min(followings.length, 1000));
+
+		const hibernatedUsers = await this.usersRepository.find({
+			where: {
+				id: In(samples.map(x => x.followerId)),
+				lastActiveDate: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 50))),
+			},
+			select: ['id'],
+		});
+
+		if (hibernatedUsers.length > 0) {
+			this.usersRepository.update({
+				id: In(hibernatedUsers.map(x => x.id)),
+			}, {
+				isHibernated: true,
+			});
+
+			this.followingsRepository.update({
+				followerId: In(hibernatedUsers.map(x => x.id)),
+			}, {
+				isFollowerHibernated: true,
+			});
+		}
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.#shutdownController.abort();
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 87979f22ac581e54c7dba357e649868b04962b72..9a817ffd76ccccc07362de47feb5ef110f869786 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -64,12 +64,6 @@ export class NoteDeleteService {
 		const deletedAt = new Date();
 		const cascadingNotes = await this.findCascadingNotes(note);
 
-		// この投稿を除く指定したユーザーによる指定したノートのリノートが存在しないとき
-		if (note.renoteId && (await this.noteEntityService.countSameRenotes(user.id, note.renoteId, note.id)) === 0) {
-			this.notesRepository.decrement({ id: note.renoteId }, 'renoteCount', 1);
-			if (!user.isBot) this.notesRepository.decrement({ id: note.renoteId }, 'score', 1);
-		}
-
 		if (note.replyId) {
 			await this.notesRepository.decrement({ id: note.replyId }, 'repliesCount', 1);
 		}
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index ca05989a4a67749a2ca428d3136f32f3695722c1..32d54d257688865235a236da81d436d0ad7383ab 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -99,19 +99,19 @@ export class NotificationService implements OnApplicationShutdown {
 			}
 
 			if (recieveConfig?.type === 'following') {
-				const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId));
+				const isFollowing = await this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId));
 				if (!isFollowing) {
 					return null;
 				}
 			} else if (recieveConfig?.type === 'follower') {
-				const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId));
+				const isFollower = await this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId));
 				if (!isFollower) {
 					return null;
 				}
 			} else if (recieveConfig?.type === 'mutualFollow') {
 				const [isFollowing, isFollower] = await Promise.all([
-					this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => followings.has(notifierId)),
-					this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => followings.has(notifieeId)),
+					this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
+					this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
 				]);
 				if (!isFollowing && !isFollower) {
 					return null;
diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts
index 940aa98347eb885f14afece677bb9604d250904e..570f2350f11a66d3c7e1117fdeabc357f4893494 100644
--- a/packages/backend/src/core/PollService.ts
+++ b/packages/backend/src/core/PollService.ts
@@ -96,6 +96,8 @@ export class PollService {
 		const note = await this.notesRepository.findOneBy({ id: noteId });
 		if (note == null) throw new Error('note not found');
 
+		if (note.localOnly) return;
+
 		const user = await this.usersRepository.findOneBy({ id: note.userId });
 		if (user == null) throw new Error('note not found');
 
diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts
index 9145726f86f9f720f4f213d1314b8033eaf23cda..50d1d2e65b97217530d7836e45ce7c9fb46ecd88 100644
--- a/packages/backend/src/core/QueryService.ts
+++ b/packages/backend/src/core/QueryService.ts
@@ -7,8 +7,9 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Brackets, ObjectLiteral } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { MiUser } from '@/models/User.js';
-import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, MutedNotesRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
+import type { UserProfilesRepository, FollowingsRepository, ChannelFollowingsRepository, BlockingsRepository, NoteThreadMutingsRepository, MutingsRepository, RenoteMutingsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
 import type { SelectQueryBuilder } from 'typeorm';
 
 @Injectable()
@@ -23,9 +24,6 @@ export class QueryService {
 		@Inject(DI.channelFollowingsRepository)
 		private channelFollowingsRepository: ChannelFollowingsRepository,
 
-		@Inject(DI.mutedNotesRepository)
-		private mutedNotesRepository: MutedNotesRepository,
-
 		@Inject(DI.blockingsRepository)
 		private blockingsRepository: BlockingsRepository,
 
@@ -37,6 +35,8 @@ export class QueryService {
 
 		@Inject(DI.renoteMutingsRepository)
 		private renoteMutingsRepository: RenoteMutingsRepository,
+
+		private idService: IdService,
 	) {
 	}
 
@@ -52,15 +52,15 @@ export class QueryService {
 			q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId });
 			q.orderBy(`${q.alias}.id`, 'DESC');
 		} else if (sinceDate && untilDate) {
-			q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) });
-			q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) });
-			q.orderBy(`${q.alias}.createdAt`, 'DESC');
+			q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.genId(new Date(sinceDate)) });
+			q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.genId(new Date(untilDate)) });
+			q.orderBy(`${q.alias}.id`, 'DESC');
 		} else if (sinceDate) {
-			q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) });
-			q.orderBy(`${q.alias}.createdAt`, 'ASC');
+			q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: this.idService.genId(new Date(sinceDate)) });
+			q.orderBy(`${q.alias}.id`, 'ASC');
 		} else if (untilDate) {
-			q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) });
-			q.orderBy(`${q.alias}.createdAt`, 'DESC');
+			q.andWhere(`${q.alias}.id < :untilId`, { untilId: this.idService.genId(new Date(untilDate)) });
+			q.orderBy(`${q.alias}.id`, 'DESC');
 		} else {
 			q.orderBy(`${q.alias}.id`, 'DESC');
 		}
@@ -79,13 +79,15 @@ export class QueryService {
 		// 投稿の引用元の作者にブロックされていない
 		q
 			.andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`)
-			.andWhere(new Brackets(qb => { qb
-				.where('note.replyUserId IS NULL')
-				.orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('note.replyUserId IS NULL')
+					.orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`);
 			}))
-			.andWhere(new Brackets(qb => { qb
-				.where('note.renoteUserId IS NULL')
-				.orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('note.renoteUserId IS NULL')
+					.orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`);
 			}));
 
 		q.setParameters(blockingQuery.getParameters());
@@ -108,39 +110,6 @@ export class QueryService {
 		q.setParameters(blockedQuery.getParameters());
 	}
 
-	@bindThis
-	public generateChannelQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
-		if (me == null) {
-			q.andWhere('note.channelId IS NULL');
-		} else {
-			q.leftJoinAndSelect('note.channel', 'channel');
-
-			const channelFollowingQuery = this.channelFollowingsRepository.createQueryBuilder('channelFollowing')
-				.select('channelFollowing.followeeId')
-				.where('channelFollowing.followerId = :followerId', { followerId: me.id });
-
-			q.andWhere(new Brackets(qb => { qb
-				// チャンネルのノートではない
-				.where('note.channelId IS NULL')
-				// または自分がフォローしているチャンネルのノート
-				.orWhere(`note.channelId IN (${ channelFollowingQuery.getQuery() })`);
-			}));
-
-			q.setParameters(channelFollowingQuery.getParameters());
-		}
-	}
-
-	@bindThis
-	public generateMutedNoteQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
-		const mutedQuery = this.mutedNotesRepository.createQueryBuilder('muted')
-			.select('muted.noteId')
-			.where('muted.userId = :userId', { userId: me.id });
-
-		q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
-
-		q.setParameters(mutedQuery.getParameters());
-	}
-
 	@bindThis
 	public generateMutedNoteThreadQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
 		const mutedQuery = this.noteThreadMutingsRepository.createQueryBuilder('threadMuted')
@@ -148,16 +117,17 @@ export class QueryService {
 			.where('threadMuted.userId = :userId', { userId: me.id });
 
 		q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`);
-		q.andWhere(new Brackets(qb => { qb
-			.where('note.threadId IS NULL')
-			.orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`);
+		q.andWhere(new Brackets(qb => {
+			qb
+				.where('note.threadId IS NULL')
+				.orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`);
 		}));
 
 		q.setParameters(mutedQuery.getParameters());
 	}
 
 	@bindThis
-	public generateMutedUserQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }, exclude?: MiUser): void {
+	public generateMutedUserQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }, exclude?: { id: MiUser['id'] }): void {
 		const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
 			.select('muting.muteeId')
 			.where('muting.muterId = :muterId', { muterId: me.id });
@@ -175,26 +145,31 @@ export class QueryService {
 		// 投稿の引用元の作者をミュートしていない
 		q
 			.andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`)
-			.andWhere(new Brackets(qb => { qb
-				.where('note.replyUserId IS NULL')
-				.orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('note.replyUserId IS NULL')
+					.orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`);
 			}))
-			.andWhere(new Brackets(qb => { qb
-				.where('note.renoteUserId IS NULL')
-				.orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('note.renoteUserId IS NULL')
+					.orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`);
 			}))
 			// mute instances
-			.andWhere(new Brackets(qb => { qb
-				.andWhere('note.userHost IS NULL')
-				.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.andWhere('note.userHost IS NULL')
+					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.userHost)`);
 			}))
-			.andWhere(new Brackets(qb => { qb
-				.where('note.replyUserHost IS NULL')
-				.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('note.replyUserHost IS NULL')
+					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.replyUserHost)`);
 			}))
-			.andWhere(new Brackets(qb => { qb
-				.where('note.renoteUserHost IS NULL')
-				.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('note.renoteUserHost IS NULL')
+					.orWhere(`NOT ((${ mutingInstanceQuery.getQuery() })::jsonb ? note.renoteUserHost)`);
 			}));
 
 		q.setParameters(mutingQuery.getParameters());
@@ -212,66 +187,45 @@ export class QueryService {
 		q.setParameters(mutingQuery.getParameters());
 	}
 
-	@bindThis
-	public generateRepliesQuery(q: SelectQueryBuilder<any>, withReplies: boolean, me?: Pick<MiUser, 'id'> | null): void {
-		if (me == null) {
-			q.andWhere(new Brackets(qb => { qb
-				.where('note.replyId IS NULL') // 返信ではない
-				.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
-					.where('note.replyId IS NOT NULL')
-					.andWhere('note.replyUserId = note.userId');
-				}));
-			}));
-		} else if (!withReplies) {
-			q.andWhere(new Brackets(qb => { qb
-				.where('note.replyId IS NULL') // 返信ではない
-				.orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信
-				.orWhere(new Brackets(qb => { qb // 返信だけど自分の行った返信
-					.where('note.replyId IS NOT NULL')
-					.andWhere('note.userId = :meId', { meId: me.id });
-				}))
-				.orWhere(new Brackets(qb => { qb // 返信だけど投稿者自身への返信
-					.where('note.replyId IS NOT NULL')
-					.andWhere('note.replyUserId = note.userId');
-				}));
-			}));
-		}
-	}
-
 	@bindThis
 	public generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: MiUser['id'] } | null): void {
 		// This code must always be synchronized with the checks in Notes.isVisibleForMe.
 		if (me == null) {
-			q.andWhere(new Brackets(qb => { qb
-				.where('note.visibility = \'public\'')
-				.orWhere('note.visibility = \'home\'');
+			q.andWhere(new Brackets(qb => {
+				qb
+					.where('note.visibility = \'public\'')
+					.orWhere('note.visibility = \'home\'');
 			}));
 		} else {
 			const followingQuery = this.followingsRepository.createQueryBuilder('following')
 				.select('following.followeeId')
 				.where('following.followerId = :meId');
 
-			q.andWhere(new Brackets(qb => { qb
+			q.andWhere(new Brackets(qb => {
+				qb
 				// 公開投稿である
-				.where(new Brackets(qb => { qb
-					.where('note.visibility = \'public\'')
-					.orWhere('note.visibility = \'home\'');
-				}))
+					.where(new Brackets(qb => {
+						qb
+							.where('note.visibility = \'public\'')
+							.orWhere('note.visibility = \'home\'');
+					}))
 				// または 自分自身
-				.orWhere('note.userId = :meId')
+					.orWhere('note.userId = :meId')
 				// または 自分宛て
-				.orWhere(':meId = ANY(note.visibleUserIds)')
-				.orWhere(':meId = ANY(note.mentions)')
-				.orWhere(new Brackets(qb => { qb
-					// または フォロワー宛ての投稿であり、
-					.where('note.visibility = \'followers\'')
-					.andWhere(new Brackets(qb => { qb
-						// 自分がフォロワーである
-						.where(`note.userId IN (${ followingQuery.getQuery() })`)
-						// または 自分の投稿へのリプライ
-						.orWhere('note.replyUserId = :meId');
+					.orWhere(':meId = ANY(note.visibleUserIds)')
+					.orWhere(':meId = ANY(note.mentions)')
+					.orWhere(new Brackets(qb => {
+						qb
+						// または フォロワー宛ての投稿であり、
+							.where('note.visibility = \'followers\'')
+							.andWhere(new Brackets(qb => {
+								qb
+								// 自分がフォロワーである
+									.where(`note.userId IN (${ followingQuery.getQuery() })`)
+								// または 自分の投稿へのリプライ
+									.orWhere('note.replyUserId = :meId');
+							}));
 					}));
-				}));
 			}));
 
 			q.setParameters({ meId: me.id });
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 25464b19a8aa8c5642ec6238c26f1bcbc372a510..e7bbd449262738ac02b3d0f1d6035054a20221e2 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -4,6 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import type { EmojisRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/_.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
@@ -26,6 +27,7 @@ import { UtilityService } from '@/core/UtilityService.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { RoleService } from '@/core/RoleService.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
 
 const FALLBACK = '❤';
 
@@ -66,6 +68,9 @@ const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
 @Injectable()
 export class ReactionService {
 	constructor(
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
@@ -86,6 +91,7 @@ export class ReactionService {
 		private noteEntityService: NoteEntityService,
 		private userBlockingService: UserBlockingService,
 		private idService: IdService,
+		private featuredService: FeaturedService,
 		private globalEventService: GlobalEventService,
 		private apRendererService: ApRendererService,
 		private apDeliverManagerService: ApDeliverManagerService,
@@ -182,11 +188,28 @@ export class ReactionService {
 		await this.notesRepository.createQueryBuilder().update()
 			.set({
 				reactions: () => sql,
-				... (!user.isBot ? { score: () => '"score" + 1' } : {}),
 			})
 			.where('id = :id', { id: note.id })
 			.execute();
 
+		// 30%の確率、セルフではない、3日以内に投稿されたノートの場合ハイライト用ランキング更新
+		if (
+			Math.random() < 0.3 &&
+			note.userId !== user.id &&
+			(Date.now() - this.idService.parse(note.id).date.getTime()) < 1000 * 60 * 60 * 24 * 3
+		) {
+			if (note.channelId != null) {
+				if (note.replyId == null) {
+					this.featuredService.updateInChannelNotesRanking(note.channelId, note.id, 1);
+				}
+			} else {
+				if (note.visibility === 'public' && note.userHost == null && note.replyId == null) {
+					this.featuredService.updateGlobalNotesRanking(note.id, 1);
+					this.featuredService.updatePerUserNotesRanking(note.userId, note.id, 1);
+				}
+			}
+		}
+
 		const meta = await this.metaService.fetch();
 
 		if (meta.enableChartsForRemoteUser || (user.host == null)) {
@@ -275,8 +298,6 @@ export class ReactionService {
 			.where('id = :id', { id: note.id })
 			.execute();
 
-		if (!user.isBot) this.notesRepository.decrement({ id: note.id }, 'score', 1);
-
 		this.globalEventService.publishNoteStream(note.id, 'unreacted', {
 			reaction: this.decodeReaction(exist.reaction).reaction,
 			userId: user.id,
diff --git a/packages/backend/src/core/RedisTimelineService.ts b/packages/backend/src/core/RedisTimelineService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..94541759cc08d07f8852654e486c56148462118e
--- /dev/null
+++ b/packages/backend/src/core/RedisTimelineService.ts
@@ -0,0 +1,80 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+
+@Injectable()
+export class RedisTimelineService {
+	constructor(
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
+		private idService: IdService,
+	) {
+	}
+
+	@bindThis
+	public push(tl: string, id: string, maxlen: number, pipeline: Redis.ChainableCommander) {
+		// リモートから遅れて届いた(もしくは後から追加された)投稿日時が古い投稿が追加されるとページネーション時に問題を引き起こすため、
+		// 3分以内に投稿されたものでない場合、Redisにある最古のIDより新しい場合のみ追加する
+		if (this.idService.parse(id).date.getTime() > Date.now() - 1000 * 60 * 3) {
+			pipeline.lpush('list:' + tl, id);
+			if (Math.random() < 0.1) { // 10%の確率でトリム
+				pipeline.ltrim('list:' + tl, 0, maxlen - 1);
+			}
+		} else {
+			// 末尾のIDを取得
+			this.redisForTimelines.lindex('list:' + tl, -1).then(lastId => {
+				if (lastId == null || (this.idService.parse(id).date.getTime() > this.idService.parse(lastId).date.getTime())) {
+					this.redisForTimelines.lpush('list:' + tl, id);
+				} else {
+					Promise.resolve();
+				}
+			});
+		}
+	}
+
+	@bindThis
+	public get(name: string, untilId?: string | null, sinceId?: string | null) {
+		if (untilId && sinceId) {
+			return this.redisForTimelines.lrange('list:' + name, 0, -1)
+				.then(ids => ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1));
+		} else if (untilId) {
+			return this.redisForTimelines.lrange('list:' + name, 0, -1)
+				.then(ids => ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1));
+		} else if (sinceId) {
+			return this.redisForTimelines.lrange('list:' + name, 0, -1)
+				.then(ids => ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1));
+		} else {
+			return this.redisForTimelines.lrange('list:' + name, 0, -1)
+				.then(ids => ids.sort((a, b) => a > b ? -1 : 1));
+		}
+	}
+
+	@bindThis
+	public getMulti(name: string[], untilId?: string | null, sinceId?: string | null): Promise<string[][]> {
+		const pipeline = this.redisForTimelines.pipeline();
+		for (const n of name) {
+			pipeline.lrange('list:' + n, 0, -1);
+		}
+		return pipeline.exec().then(res => {
+			if (res == null) return [];
+			const tls = res.map(r => r[1] as string[]);
+			return tls.map(ids =>
+				(untilId && sinceId)
+					? ids.filter(id => id < untilId && id > sinceId).sort((a, b) => a > b ? -1 : 1)
+					: untilId
+						? ids.filter(id => id < untilId).sort((a, b) => a > b ? -1 : 1)
+						: sinceId
+							? ids.filter(id => id > sinceId).sort((a, b) => a < b ? -1 : 1)
+							: ids.sort((a, b) => a > b ? -1 : 1),
+			);
+		});
+	}
+}
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index ad40fbaecd4ad1b067203d305afcfb89c166fabd..2c3547e4acd9d864d4dbabbc918f6dd7538127cf 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -20,13 +20,13 @@ import { IdService } from '@/core/IdService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
 import type { Packed } from '@/misc/json-schema.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
 
 export type RolePolicies = {
 	gtlAvailable: boolean;
 	ltlAvailable: boolean;
 	canPublicNote: boolean;
-	canEditNote: boolean;
 	canInvite: boolean;
 	inviteLimit: number;
 	inviteLimitCycle: number;
@@ -52,7 +52,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
 	gtlAvailable: true,
 	ltlAvailable: true,
 	canPublicNote: true,
-	canEditNote: true,
 	canInvite: false,
 	inviteLimit: 0,
 	inviteLimitCycle: 60 * 24 * 7,
@@ -104,6 +103,7 @@ export class RoleService implements OnApplicationShutdown {
 		private globalEventService: GlobalEventService,
 		private idService: IdService,
 		private moderationLogService: ModerationLogService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		//this.onMessage = this.onMessage.bind(this);
 
@@ -298,7 +298,6 @@ export class RoleService implements OnApplicationShutdown {
 			gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
 			ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
 			canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
-			canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
 			canInvite: calc('canInvite', vs => vs.some(v => v === true)),
 			inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
 			inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
@@ -475,12 +474,7 @@ export class RoleService implements OnApplicationShutdown {
 		const redisPipeline = this.redisClient.pipeline();
 
 		for (const role of roles) {
-			redisPipeline.xadd(
-				`roleTimeline:${role.id}`,
-				'MAXLEN', '~', '1000',
-				'*',
-				'note', note.id);
-
+			this.redisTimelineService.push(`roleTimeline:${role.id}`, note.id, 1000, redisPipeline);
 			this.globalEventService.publishRoleTimelineStream(role.id, 'note', note);
 		}
 
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 37031e341e4f20cc223d00cd8e5fc4655089bef3..087dfd92147ceba7ad1fce485248e2f89e170ed0 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -11,7 +11,7 @@ import type { MiBlocking } from '@/models/Blocking.js';
 import { QueueService } from '@/core/QueueService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
-import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js';
+import type { FollowRequestsRepository, BlockingsRepository, UserListsRepository, UserListMembershipsRepository } from '@/models/_.js';
 import Logger from '@/logger.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@@ -38,8 +38,8 @@ export class UserBlockingService implements OnModuleInit {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private cacheService: CacheService,
 		private userEntityService: UserEntityService,
@@ -149,7 +149,7 @@ export class UserBlockingService implements OnModuleInit {
 		});
 
 		for (const userList of userLists) {
-			await this.userListJoiningsRepository.delete({
+			await this.userListMembershipsRepository.delete({
 				userListId: userList.id,
 				userId: user.id,
 			});
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 230f6ef261b8997ead7f5c8012ed2e542bfb08ec..beffcc2e9c9720678a00d5eadb1a02d7eaed9f93 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -123,7 +123,11 @@ export class UserFollowingService implements OnModuleInit {
 		// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
 		// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
 		// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
-		if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee))) {
+		if (
+			followee.isLocked ||
+			(followeeProfile.carefulBot && follower.isBot) ||
+			(this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee) && process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING !== 'true')
+		) {
 			let autoAccept = false;
 
 			// 鍵アカウントであっても、既にフォローされていた場合はスルー
diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts
index 93dc5edbbafe8214157f0c03ba6946ce075c26f0..5b4e7a711eca30c9eb5659e1591d2e8c2bf906a0 100644
--- a/packages/backend/src/core/UserListService.ts
+++ b/packages/backend/src/core/UserListService.ts
@@ -5,10 +5,10 @@
 
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import type { UserListJoiningsRepository } from '@/models/_.js';
+import type { UserListMembershipsRepository } from '@/models/_.js';
 import type { MiUser } from '@/models/User.js';
 import type { MiUserList } from '@/models/UserList.js';
-import type { MiUserListJoining } from '@/models/UserListJoining.js';
+import type { MiUserListMembership } from '@/models/UserListMembership.js';
 import { IdService } from '@/core/IdService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
@@ -33,8 +33,8 @@ export class UserListService implements OnApplicationShutdown {
 		@Inject(DI.redisForSub)
 		private redisForSub: Redis.Redis,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private userEntityService: UserEntityService,
 		private idService: IdService,
@@ -46,7 +46,7 @@ export class UserListService implements OnApplicationShutdown {
 		this.membersCache = new RedisKVCache<Set<string>>(this.redisClient, 'userListMembers', {
 			lifetime: 1000 * 60 * 30, // 30m
 			memoryCacheLifetime: 1000 * 60, // 1m
-			fetcher: (key) => this.userListJoiningsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))),
+			fetcher: (key) => this.userListMembershipsRepository.find({ where: { userListId: key }, select: ['userId'] }).then(xs => new Set(xs.map(x => x.userId))),
 			toRedisConverter: (value) => JSON.stringify(Array.from(value)),
 			fromRedisConverter: (value) => new Set(JSON.parse(value)),
 		});
@@ -85,19 +85,20 @@ export class UserListService implements OnApplicationShutdown {
 
 	@bindThis
 	public async addMember(target: MiUser, list: MiUserList, me: MiUser) {
-		const currentCount = await this.userListJoiningsRepository.countBy({
+		const currentCount = await this.userListMembershipsRepository.countBy({
 			userListId: list.id,
 		});
 		if (currentCount > (await this.roleService.getUserPolicies(me.id)).userEachUserListsLimit) {
 			throw new UserListService.TooManyUsersError();
 		}
 
-		await this.userListJoiningsRepository.insert({
+		await this.userListMembershipsRepository.insert({
 			id: this.idService.genId(),
 			createdAt: new Date(),
 			userId: target.id,
 			userListId: list.id,
-		} as MiUserListJoining);
+			userListUserId: list.userId,
+		} as MiUserListMembership);
 
 		this.globalEventService.publishInternalEvent('userListMemberAdded', { userListId: list.id, memberId: target.id });
 		this.globalEventService.publishUserListStream(list.id, 'userAdded', await this.userEntityService.pack(target));
@@ -113,7 +114,7 @@ export class UserListService implements OnApplicationShutdown {
 
 	@bindThis
 	public async removeMember(target: MiUser, list: MiUserList) {
-		await this.userListJoiningsRepository.delete({
+		await this.userListMembershipsRepository.delete({
 			userId: target.id,
 			userListId: list.id,
 		});
@@ -122,6 +123,24 @@ export class UserListService implements OnApplicationShutdown {
 		this.globalEventService.publishUserListStream(list.id, 'userRemoved', await this.userEntityService.pack(target));
 	}
 
+	@bindThis
+	public async updateMembership(target: MiUser, list: MiUserList, options: { withReplies?: boolean }) {
+		const membership = await this.userListMembershipsRepository.findOneBy({
+			userId: target.id,
+			userListId: list.id,
+		});
+
+		if (membership == null) {
+			throw new Error('User is not a member of the list');
+		}
+
+		await this.userListMembershipsRepository.update({
+			id: membership.id,
+		}, {
+			withReplies: options.withReplies,
+		});
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.redisForSub.off('message', this.onMessage);
diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d16e1be61513e0e77b774a96bbe790a0481d3772
--- /dev/null
+++ b/packages/backend/src/core/UserService.ts
@@ -0,0 +1,53 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { FollowingsRepository, UsersRepository } from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+
+@Injectable()
+export class UserService {
+	constructor(
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.followingsRepository)
+		private followingsRepository: FollowingsRepository,
+	) {
+	}
+
+	@bindThis
+	public async updateLastActiveDate(user: MiUser): Promise<void> {
+		if (user.isHibernated) {
+			const result = await this.usersRepository.createQueryBuilder().update()
+				.set({
+					lastActiveDate: new Date(),
+				})
+				.where('id = :id', { id: user.id })
+				.returning('*')
+				.execute()
+				.then((response) => {
+					return response.raw[0];
+				});
+			const wokeUp = result.isHibernated;
+			if (wokeUp) {
+				this.usersRepository.update(user.id, {
+					isHibernated: false,
+				});
+				this.followingsRepository.update({
+					followerId: user.id,
+				}, {
+					isFollowerHibernated: false,
+				});
+			}
+		} else {
+			this.usersRepository.update(user.id, {
+				lastActiveDate: new Date(),
+			});
+		}
+	}
+}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index a024286b480719c754821c5f23db298ccebc3f66..abe4aafd6e348914157e88138da38ed95614f7b5 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -17,6 +17,7 @@ import type { MiNoteReaction } from '@/models/NoteReaction.js';
 import type { UsersRepository, NotesRepository, FollowingsRepository, PollsRepository, PollVotesRepository, NoteReactionsRepository, ChannelsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import { isNotNull } from '@/misc/is-not-null.js';
+import { DebounceLoader } from '@/misc/loader.js';
 import type { OnModuleInit } from '@nestjs/common';
 import type { CustomEmojiService } from '../CustomEmojiService.js';
 import type { ReactionService } from '../ReactionService.js';
@@ -29,6 +30,7 @@ export class NoteEntityService implements OnModuleInit {
 	private driveFileEntityService: DriveFileEntityService;
 	private customEmojiService: CustomEmojiService;
 	private reactionService: ReactionService;
+	private noteLoader = new DebounceLoader(this.findNoteOrFail);
 
 	constructor(
 		private moduleRef: ModuleRef,
@@ -98,13 +100,13 @@ export class NoteEntityService implements OnModuleInit {
 			} else if (meId === packedNote.userId) {
 				hide = false;
 			} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
-			// 自分の投稿に対するリプライ
+				// 自分の投稿に対するリプライ
 				hide = false;
 			} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
-			// 自分へのメンション
+				// 自分へのメンション
 				hide = false;
 			} else {
-			// フォロワーかどうか
+				// フォロワーかどうか
 				const isFollowing = await this.followingsRepository.exist({
 					where: {
 						followeeId: packedNote.userId,
@@ -285,7 +287,7 @@ export class NoteEntityService implements OnModuleInit {
 		}, options);
 
 		const meId = me ? me.id : null;
-		const note = typeof src === 'object' ? src : await this.notesRepository.findOneOrFail({ where: { id: src }, relations: ['user'] });
+		const note = typeof src === 'object' ? src : await this.noteLoader.load(src);
 		const host = note.userHost;
 
 		let text = note.text;
@@ -308,7 +310,6 @@ export class NoteEntityService implements OnModuleInit {
 		const packed: Packed<'Note'> = await awaitAll({
 			id: note.id,
 			createdAt: note.createdAt.toISOString(),
-			updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
 			userId: note.userId,
 			user: this.userEntityService.pack(note.user ?? note.userId, me, {
 				detail: false,
@@ -453,17 +454,10 @@ export class NoteEntityService implements OnModuleInit {
 	}
 
 	@bindThis
-	public async countSameRenotes(userId: string, renoteId: string, excludeNoteId: string | undefined): Promise<number> {
-		// 指定したユーザーの指定したノートのリノートがいくつあるか数える
-		const query = this.notesRepository.createQueryBuilder('note')
-			.where('note.userId = :userId', { userId })
-			.andWhere('note.renoteId = :renoteId', { renoteId });
-
-		// 指定した投稿を除く
-		if (excludeNoteId) {
-			query.andWhere('note.id != :excludeNoteId', { excludeNoteId });
-		}
-
-		return await query.getCount();
+	private findNoteOrFail(id: string): Promise<MiNote> {
+		return this.notesRepository.findOneOrFail({
+			where: { id },
+			relations: ['user'],
+		});
 	}
 }
diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts
index 23e82561d670fbf39d6cf1ffb381cc8d518146bc..79375a700838dce997fa46ada2030670fe4c7bde 100644
--- a/packages/backend/src/core/entities/RoleEntityService.ts
+++ b/packages/backend/src/core/entities/RoleEntityService.ts
@@ -33,9 +33,10 @@ export class RoleEntityService {
 
 		const assignedCount = await this.roleAssignmentsRepository.createQueryBuilder('assign')
 			.where('assign.roleId = :roleId', { roleId: role.id })
-			.andWhere(new Brackets(qb => { qb
-				.where('assign.expiresAt IS NULL')
-				.orWhere('assign.expiresAt > :now', { now: new Date() });
+			.andWhere(new Brackets(qb => {
+				qb
+					.where('assign.expiresAt IS NULL')
+					.orWhere('assign.expiresAt > :now', { now: new Date() });
 			}))
 			.getCount();
 
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index a47b3d51ac2bab3cde73a6c0e30807a7519e47a1..ee67634da5000fd49c4f8b9161e9d4262ead1ad1 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -146,64 +146,76 @@ export class UserEntityService implements OnModuleInit {
 
 	@bindThis
 	public async getRelation(me: MiUser['id'], target: MiUser['id']) {
-		const following = await this.followingsRepository.findOneBy({
-			followerId: me,
-			followeeId: target,
-		});
-		return awaitAll({
-			id: target,
+		const [
 			following,
-			isFollowing: following != null,
-			isFollowed: this.followingsRepository.count({
+			isFollowed,
+			hasPendingFollowRequestFromYou,
+			hasPendingFollowRequestToYou,
+			isBlocking,
+			isBlocked,
+			isMuted,
+			isRenoteMuted,
+		] = await Promise.all([
+			this.followingsRepository.findOneBy({
+				followerId: me,
+				followeeId: target,
+			}),
+			this.followingsRepository.exist({
 				where: {
 					followerId: target,
 					followeeId: me,
 				},
-				take: 1,
-			}).then(n => n > 0),
-			hasPendingFollowRequestFromYou: this.followRequestsRepository.count({
+			}),
+			this.followRequestsRepository.exist({
 				where: {
 					followerId: me,
 					followeeId: target,
 				},
-				take: 1,
-			}).then(n => n > 0),
-			hasPendingFollowRequestToYou: this.followRequestsRepository.count({
+			}),
+			this.followRequestsRepository.exist({
 				where: {
 					followerId: target,
 					followeeId: me,
 				},
-				take: 1,
-			}).then(n => n > 0),
-			isBlocking: this.blockingsRepository.count({
+			}),
+			this.blockingsRepository.exist({
 				where: {
 					blockerId: me,
 					blockeeId: target,
 				},
-				take: 1,
-			}).then(n => n > 0),
-			isBlocked: this.blockingsRepository.count({
+			}),
+			this.blockingsRepository.exist({
 				where: {
 					blockerId: target,
 					blockeeId: me,
 				},
-				take: 1,
-			}).then(n => n > 0),
-			isMuted: this.mutingsRepository.count({
+			}),
+			this.mutingsRepository.exist({
 				where: {
 					muterId: me,
 					muteeId: target,
 				},
-				take: 1,
-			}).then(n => n > 0),
-			isRenoteMuted: this.renoteMutingsRepository.count({
+			}),
+			this.renoteMutingsRepository.exist({
 				where: {
 					muterId: me,
 					muteeId: target,
 				},
-				take: 1,
-			}).then(n => n > 0),
-		});
+			}),
+		]);
+
+		return {
+			id: target,
+			following,
+			isFollowing: following != null,
+			isFollowed,
+			hasPendingFollowRequestFromYou,
+			hasPendingFollowRequestToYou,
+			isBlocking,
+			isBlocked,
+			isMuted,
+			isRenoteMuted,
+		};
 	}
 
 	@bindThis
@@ -290,24 +302,6 @@ export class UserEntityService implements OnModuleInit {
 
 		const user = typeof src === 'object' ? src : await this.usersRepository.findOneByOrFail({ id: src });
 
-		// migration
-		if (user.avatarId != null && user.avatarUrl === null) {
-			const avatar = await this.driveFilesRepository.findOneByOrFail({ id: user.avatarId });
-			user.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
-			this.usersRepository.update(user.id, {
-				avatarUrl: user.avatarUrl,
-				avatarBlurhash: avatar.blurhash,
-			});
-		}
-		if (user.bannerId != null && user.bannerUrl === null) {
-			const banner = await this.driveFilesRepository.findOneByOrFail({ id: user.bannerId });
-			user.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
-			this.usersRepository.update(user.id, {
-				bannerUrl: user.bannerUrl,
-				bannerBlurhash: banner.blurhash,
-			});
-		}
-
 		const meId = me ? me.id : null;
 		const isMe = meId === user.id;
 		const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
@@ -487,6 +481,7 @@ export class UserEntityService implements OnModuleInit {
 				isMuted: relation.isMuted,
 				isRenoteMuted: relation.isRenoteMuted,
 				notify: relation.following?.notify ?? 'none',
+				withReplies: relation.following?.withReplies ?? false,
 			} : {}),
 		} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
 
diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts
index a7f28851943f935d7eec85ffa9c6abffcab1a4d0..06b6e852b1463cf531275e13962519d891340324 100644
--- a/packages/backend/src/core/entities/UserListEntityService.ts
+++ b/packages/backend/src/core/entities/UserListEntityService.ts
@@ -5,11 +5,12 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
+import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
 import type { Packed } from '@/misc/json-schema.js';
 import type { } from '@/models/Blocking.js';
 import type { MiUserList } from '@/models/UserList.js';
 import { bindThis } from '@/decorators.js';
+import { UserEntityService } from './UserEntityService.js';
 
 @Injectable()
 export class UserListEntityService {
@@ -17,8 +18,10 @@ export class UserListEntityService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
+
+		private userEntityService: UserEntityService,
 	) {
 	}
 
@@ -28,7 +31,7 @@ export class UserListEntityService {
 	): Promise<Packed<'UserList'>> {
 		const userList = typeof src === 'object' ? src : await this.userListsRepository.findOneByOrFail({ id: src });
 
-		const users = await this.userListJoiningsRepository.findBy({
+		const users = await this.userListMembershipsRepository.findBy({
 			userListId: userList.id,
 		});
 
@@ -40,5 +43,18 @@ export class UserListEntityService {
 			isPublic: userList.isPublic,
 		};
 	}
+
+	@bindThis
+	public async packMembershipsMany(
+		memberships: MiUserListMembership[],
+	) {
+		return Promise.all(memberships.map(async x => ({
+			id: x.id,
+			createdAt: x.createdAt.toISOString(),
+			userId: x.userId,
+			user: await this.userEntityService.pack(x.userId),
+			withReplies: x.withReplies,
+		})));
+	}
 }
 
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 72ec98cebe890192ea63d595eaafdff9989ecf6b..edcdd21d606ac748d68793e0f1559c869cf7dbf9 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -10,6 +10,7 @@ export const DI = {
 	redis: Symbol('redis'),
 	redisForPub: Symbol('redisForPub'),
 	redisForSub: Symbol('redisForSub'),
+	redisForTimelines: Symbol('redisForTimelines'),
 
 	//#region Repositories
 	usersRepository: Symbol('usersRepository'),
@@ -30,7 +31,7 @@ export const DI = {
 	userPublickeysRepository: Symbol('userPublickeysRepository'),
 	userListsRepository: Symbol('userListsRepository'),
 	userListFavoritesRepository: Symbol('userListFavoritesRepository'),
-	userListJoiningsRepository: Symbol('userListJoiningsRepository'),
+	userListMembershipsRepository: Symbol('userListMembershipsRepository'),
 	userNotePiningsRepository: Symbol('userNotePiningsRepository'),
 	userIpsRepository: Symbol('userIpsRepository'),
 	usedUsernamesRepository: Symbol('usedUsernamesRepository'),
@@ -63,7 +64,6 @@ export const DI = {
 	promoNotesRepository: Symbol('promoNotesRepository'),
 	promoReadsRepository: Symbol('promoReadsRepository'),
 	relaysRepository: Symbol('relaysRepository'),
-	mutedNotesRepository: Symbol('mutedNotesRepository'),
 	channelsRepository: Symbol('channelsRepository'),
 	channelFollowingsRepository: Symbol('channelFollowingsRepository'),
 	channelFavoritesRepository: Symbol('channelFavoritesRepository'),
diff --git a/packages/backend/src/misc/is-user-related.ts b/packages/backend/src/misc/is-user-related.ts
index edd65a3c1cc1370977985f91fd3c5f985aece476..6efb1194d3bba974b5032635070e0442190c7162 100644
--- a/packages/backend/src/misc/is-user-related.ts
+++ b/packages/backend/src/misc/is-user-related.ts
@@ -3,16 +3,16 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-export function isUserRelated(note: any, userIds: Set<string>): boolean {
-	if (userIds.has(note.userId)) {
+export function isUserRelated(note: any, userIds: Set<string>, ignoreAuthor = false): boolean {
+	if (userIds.has(note.userId) && !ignoreAuthor) {
 		return true;
 	}
 
-	if (note.reply != null && userIds.has(note.reply.userId)) {
+	if (note.reply != null && note.reply.userId !== note.userId && userIds.has(note.reply.userId)) {
 		return true;
 	}
 
-	if (note.renote != null && userIds.has(note.renote.userId)) {
+	if (note.renote != null && note.renote.userId !== note.userId && userIds.has(note.renote.userId)) {
 		return true;
 	}
 
diff --git a/packages/backend/src/misc/loader.ts b/packages/backend/src/misc/loader.ts
new file mode 100644
index 0000000000000000000000000000000000000000..25f7b54d31c62c6c1e8d7703fe81bac4b353d8e6
--- /dev/null
+++ b/packages/backend/src/misc/loader.ts
@@ -0,0 +1,52 @@
+export type FetchFunction<K, V> = (key: K) => Promise<V>;
+
+type ResolveReject<V> = Parameters<ConstructorParameters<typeof Promise<V>>[0]>;
+
+type ResolverPair<V> = {
+	resolve: ResolveReject<V>[0];
+	reject: ResolveReject<V>[1];
+};
+
+export class DebounceLoader<K, V> {
+	private resolverMap = new Map<K, ResolverPair<V>>();
+	private promiseMap = new Map<K, Promise<V>>();
+	private resolvedPromise = Promise.resolve();
+	constructor(private loadFn: FetchFunction<K, V>) {}
+
+	public load(key: K): Promise<V> {
+		const promise = this.promiseMap.get(key);
+		if (typeof promise !== 'undefined') {
+			return promise;
+		}
+
+		const isFirst = this.promiseMap.size === 0;
+		const newPromise = new Promise<V>((resolve, reject) => {
+			this.resolverMap.set(key, { resolve, reject });
+		});
+		this.promiseMap.set(key, newPromise);
+
+		if (isFirst) {
+			this.enqueueDebouncedLoadJob();
+		}
+
+		return newPromise;
+	}
+
+	private runDebouncedLoad(): void {
+		const resolvers = [...this.resolverMap];
+		this.resolverMap.clear();
+		this.promiseMap.clear();
+
+		for (const [key, { resolve, reject }] of resolvers) {
+			this.loadFn(key).then(resolve, reject);
+		}
+	}
+
+	private enqueueDebouncedLoadJob(): void {
+		this.resolvedPromise.then(() => {
+			process.nextTick(() => {
+				this.runDebouncedLoad();
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts
index 8c9f965fadd4093d98b5c9d27a1a2ff8a5ab0d75..607538b1e79cf50e5df68e3f320f59315e36eb10 100644
--- a/packages/backend/src/models/Following.ts
+++ b/packages/backend/src/models/Following.ts
@@ -9,6 +9,7 @@ import { MiUser } from './User.js';
 
 @Entity('following')
 @Index(['followerId', 'followeeId'], { unique: true })
+@Index(['followeeId', 'followerHost', 'isFollowerHibernated'])
 export class MiFollowing {
 	@PrimaryColumn(id())
 	public id: string;
@@ -45,6 +46,17 @@ export class MiFollowing {
 	@JoinColumn()
 	public follower: MiUser | null;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public isFollowerHibernated: boolean;
+
+	// タイムラインにその人のリプライまで含めるかどうか
+	@Column('boolean', {
+		default: false,
+	})
+	public withReplies: boolean;
+
 	@Index()
 	@Column('varchar', {
 		length: 32,
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index e69bef8e9845f5568b2ed45143e03ecd28016ecf..d2bd0c26e99d9fb04bce90e2e8551bd53ad0fca5 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -335,6 +335,18 @@ export class MiMeta {
 	})
 	public feedbackUrl: string | null;
 
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public impressumUrl: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public privacyPolicyUrl: string | null;
+
 	@Column('varchar', {
 		length: 8192,
 		nullable: true,
@@ -471,4 +483,29 @@ export class MiMeta {
 		length: 1024, array: true, default: '{ "admin", "administrator", "root", "system", "maintainer", "host", "mod", "moderator", "owner", "superuser", "staff", "auth", "i", "me", "everyone", "all", "mention", "mentions", "example", "user", "users", "account", "accounts", "official", "help", "helps", "support", "supports", "info", "information", "informations", "announce", "announces", "announcement", "announcements", "notice", "notification", "notifications", "dev", "developer", "developers", "tech", "misskey" }',
 	})
 	public preservedUsernames: string[];
+
+	@Column('integer', {
+		default: 300,
+	})
+	public perLocalUserUserTimelineCacheMax: number;
+
+	@Column('integer', {
+		default: 100,
+	})
+	public perRemoteUserUserTimelineCacheMax: number;
+
+	@Column('integer', {
+		default: 300,
+	})
+	public perUserHomeTimelineCacheMax: number;
+
+	@Column('integer', {
+		default: 300,
+	})
+	public perUserListTimelineCacheMax: number;
+
+	@Column('integer', {
+		default: 0,
+	})
+	public notesPerOneAd: number;
 }
diff --git a/packages/backend/src/models/MutedNote.ts b/packages/backend/src/models/MutedNote.ts
deleted file mode 100644
index 89a678a2a77805c4a18eb2296af418b351bffca3..0000000000000000000000000000000000000000
--- a/packages/backend/src/models/MutedNote.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Entity, Index, JoinColumn, Column, ManyToOne, PrimaryColumn } from 'typeorm';
-import { mutedNoteReasons } from '@/types.js';
-import { id } from './util/id.js';
-import { MiNote } from './Note.js';
-import { MiUser } from './User.js';
-
-@Entity('muted_note')
-@Index(['noteId', 'userId'], { unique: true })
-export class MiMutedNote {
-	@PrimaryColumn(id())
-	public id: string;
-
-	@Index()
-	@Column({
-		...id(),
-		comment: 'The note ID.',
-	})
-	public noteId: MiNote['id'];
-
-	@ManyToOne(type => MiNote, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public note: MiNote | null;
-
-	@Index()
-	@Column({
-		...id(),
-		comment: 'The user ID.',
-	})
-	public userId: MiUser['id'];
-
-	@ManyToOne(type => MiUser, {
-		onDelete: 'CASCADE',
-	})
-	@JoinColumn()
-	public user: MiUser | null;
-
-	/**
-	 * ミュートされた理由。
-	 */
-	@Index()
-	@Column('enum', {
-		enum: mutedNoteReasons,
-		comment: 'The reason of the MutedNote.',
-	})
-	public reason: typeof mutedNoteReasons[number];
-}
diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts
index f396a0cd7a83c3a50da88411d75092ef39daeac5..3e2adf4d82e633830002e0b379e3d91d257a5849 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -18,17 +18,11 @@ export class MiNote {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Index()
 	@Column('timestamp with time zone', {
 		comment: 'The created date of the Note.',
 	})
 	public createdAt: Date;
 
-	@Column('timestamp with time zone', {
-		default: null,
-	})
-	public updatedAt: Date | null;
-
 	@Index()
 	@Column({
 		...id(),
@@ -144,11 +138,6 @@ export class MiNote {
 	})
 	public url: string | null;
 
-	@Column('integer', {
-		default: 0, select: false,
-	})
-	public score: number;
-
 	@Index()
 	@Column({
 		...id(),
@@ -156,7 +145,6 @@ export class MiNote {
 	})
 	public fileIds: MiDriveFile['id'][];
 
-	@Index()
 	@Column('varchar', {
 		length: 256, array: true, default: '{}',
 	})
diff --git a/packages/backend/src/models/NoteReaction.ts b/packages/backend/src/models/NoteReaction.ts
index 7c08d31c6d7866434609007b7931c1e16680c874..43323f8a438c92d06009d7bf023a194695807f90 100644
--- a/packages/backend/src/models/NoteReaction.ts
+++ b/packages/backend/src/models/NoteReaction.ts
@@ -14,7 +14,6 @@ export class MiNoteReaction {
 	@PrimaryColumn(id())
 	public id: string;
 
-	@Index()
 	@Column('timestamp with time zone', {
 		comment: 'The created date of the NoteReaction.',
 	})
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 766e7ce21c70656791683cf857af821d6b7a9efa..9efd6841b180e3674c6b4e95e1cb88be1a8ab47e 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -5,7 +5,7 @@
 
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMutedNote, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListJoining, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
+import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook } from './_.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -117,9 +117,9 @@ const $userListFavoritesRepository: Provider = {
 	inject: [DI.db],
 };
 
-const $userListJoiningsRepository: Provider = {
-	provide: DI.userListJoiningsRepository,
-	useFactory: (db: DataSource) => db.getRepository(MiUserListJoining),
+const $userListMembershipsRepository: Provider = {
+	provide: DI.userListMembershipsRepository,
+	useFactory: (db: DataSource) => db.getRepository(MiUserListMembership),
 	inject: [DI.db],
 };
 
@@ -315,12 +315,6 @@ const $relaysRepository: Provider = {
 	inject: [DI.db],
 };
 
-const $mutedNotesRepository: Provider = {
-	provide: DI.mutedNotesRepository,
-	useFactory: (db: DataSource) => db.getRepository(MiMutedNote),
-	inject: [DI.db],
-};
-
 const $channelsRepository: Provider = {
 	provide: DI.channelsRepository,
 	useFactory: (db: DataSource) => db.getRepository(MiChannel),
@@ -421,7 +415,7 @@ const $userMemosRepository: Provider = {
 		$userPublickeysRepository,
 		$userListsRepository,
 		$userListFavoritesRepository,
-		$userListJoiningsRepository,
+		$userListMembershipsRepository,
 		$userNotePiningsRepository,
 		$userIpsRepository,
 		$usedUsernamesRepository,
@@ -454,7 +448,6 @@ const $userMemosRepository: Provider = {
 		$promoNotesRepository,
 		$promoReadsRepository,
 		$relaysRepository,
-		$mutedNotesRepository,
 		$channelsRepository,
 		$channelFollowingsRepository,
 		$channelFavoritesRepository,
@@ -488,7 +481,7 @@ const $userMemosRepository: Provider = {
 		$userPublickeysRepository,
 		$userListsRepository,
 		$userListFavoritesRepository,
-		$userListJoiningsRepository,
+		$userListMembershipsRepository,
 		$userNotePiningsRepository,
 		$userIpsRepository,
 		$usedUsernamesRepository,
@@ -521,7 +514,6 @@ const $userMemosRepository: Provider = {
 		$promoNotesRepository,
 		$promoReadsRepository,
 		$relaysRepository,
-		$mutedNotesRepository,
 		$channelsRepository,
 		$channelFollowingsRepository,
 		$channelFavoritesRepository,
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index b040d302ce7c662f2de77889174058da3875c51b..4d961c4290b9492656027b13060ded7bf264821b 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -187,6 +187,11 @@ export class MiUser {
 	})
 	public isExplorable: boolean;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public isHibernated: boolean;
+
 	// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
 	@Column('boolean', {
 		default: false,
diff --git a/packages/backend/src/models/UserListJoining.ts b/packages/backend/src/models/UserListMembership.ts
similarity index 69%
rename from packages/backend/src/models/UserListJoining.ts
rename to packages/backend/src/models/UserListMembership.ts
index 4918f2f700010562e5c7d21579893f8f65a2f95f..f57f9ac33d5bfd576cfd42a5baf6b05840a7b32c 100644
--- a/packages/backend/src/models/UserListJoining.ts
+++ b/packages/backend/src/models/UserListMembership.ts
@@ -8,14 +8,14 @@ import { id } from './util/id.js';
 import { MiUser } from './User.js';
 import { MiUserList } from './UserList.js';
 
-@Entity('user_list_joining')
+@Entity('user_list_membership')
 @Index(['userId', 'userListId'], { unique: true })
-export class MiUserListJoining {
+export class MiUserListMembership {
 	@PrimaryColumn(id())
 	public id: string;
 
 	@Column('timestamp with time zone', {
-		comment: 'The created date of the UserListJoining.',
+		comment: 'The created date of the UserListMembership.',
 	})
 	public createdAt: Date;
 
@@ -44,4 +44,17 @@ export class MiUserListJoining {
 	})
 	@JoinColumn()
 	public userList: MiUserList | null;
+
+	// タイムラインにその人のリプライまで含めるかどうか
+	@Column('boolean', {
+		default: false,
+	})
+	public withReplies: boolean;
+
+	//#region Denormalized fields
+	@Column({
+		...id(),
+	})
+	public userListUserId: MiUser['id'];
+	//#endregion
 }
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index 6be7bd0df6ba6da245611ce699f0d5f2a9b4c517..f974f95ed89fcc1c8f667da2699b6a611222847c 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -28,7 +28,6 @@ import { MiHashtag } from '@/models/Hashtag.js';
 import { MiInstance } from '@/models/Instance.js';
 import { MiMeta } from '@/models/Meta.js';
 import { MiModerationLog } from '@/models/ModerationLog.js';
-import { MiMutedNote } from '@/models/MutedNote.js';
 import { MiMuting } from '@/models/Muting.js';
 import { MiRenoteMuting } from '@/models/RenoteMuting.js';
 import { MiNote } from '@/models/Note.js';
@@ -53,7 +52,7 @@ import { MiUser } from '@/models/User.js';
 import { MiUserIp } from '@/models/UserIp.js';
 import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUserList } from '@/models/UserList.js';
-import { MiUserListJoining } from '@/models/UserListJoining.js';
+import { MiUserListMembership } from '@/models/UserListMembership.js';
 import { MiUserNotePining } from '@/models/UserNotePining.js';
 import { MiUserPending } from '@/models/UserPending.js';
 import { MiUserProfile } from '@/models/UserProfile.js';
@@ -96,7 +95,6 @@ export {
 	MiInstance,
 	MiMeta,
 	MiModerationLog,
-	MiMutedNote,
 	MiMuting,
 	MiRenoteMuting,
 	MiNote,
@@ -122,7 +120,7 @@ export {
 	MiUserKeypair,
 	MiUserList,
 	MiUserListFavorite,
-	MiUserListJoining,
+	MiUserListMembership,
 	MiUserNotePining,
 	MiUserPending,
 	MiUserProfile,
@@ -163,7 +161,6 @@ export type HashtagsRepository = Repository<MiHashtag>;
 export type InstancesRepository = Repository<MiInstance>;
 export type MetasRepository = Repository<MiMeta>;
 export type ModerationLogsRepository = Repository<MiModerationLog>;
-export type MutedNotesRepository = Repository<MiMutedNote>;
 export type MutingsRepository = Repository<MiMuting>;
 export type RenoteMutingsRepository = Repository<MiRenoteMuting>;
 export type NotesRepository = Repository<MiNote>;
@@ -189,7 +186,7 @@ export type UserIpsRepository = Repository<MiUserIp>;
 export type UserKeypairsRepository = Repository<MiUserKeypair>;
 export type UserListsRepository = Repository<MiUserList>;
 export type UserListFavoritesRepository = Repository<MiUserListFavorite>;
-export type UserListJoiningsRepository = Repository<MiUserListJoining>;
+export type UserListMembershipsRepository = Repository<MiUserListMembership>;
 export type UserNotePiningsRepository = Repository<MiUserNotePining>;
 export type UserPendingsRepository = Repository<MiUserPending>;
 export type UserProfilesRepository = Repository<MiUserProfile>;
diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts
index ad0cb3c45d6a0e6e001360b7ac2a283bfc896bce..2caf0d0c3d9f75684cb7b370cac70f91690c7dd3 100644
--- a/packages/backend/src/models/json-schema/note.ts
+++ b/packages/backend/src/models/json-schema/note.ts
@@ -17,11 +17,6 @@ export const packedNoteSchema = {
 			optional: false, nullable: false,
 			format: 'date-time',
 		},
-		updatedAt: {
-			type: 'string',
-			optional: true, nullable: true,
-			format: 'date-time',
-		},
 		deletedAt: {
 			type: 'string',
 			optional: true, nullable: true,
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 0181ea50e8dd6834b28598b96ad75335281a79f2..57d2d976ff7a5727d5d2bb2e338a8dc2ed60ff6c 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -277,6 +277,10 @@ export const packedUserDetailedNotMeOnlySchema = {
 			type: 'string',
 			nullable: false, optional: true,
 		},
+		withReplies: {
+			type: 'boolean',
+			nullable: false, optional: true,
+		},
 		//#endregion
 	},
 } as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 10126eab2bbdf0cb76692f309992ffc675d082e9..d4c6ad82ce48857f5f1be6fea7ea2a988a740524 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -36,7 +36,6 @@ import { MiHashtag } from '@/models/Hashtag.js';
 import { MiInstance } from '@/models/Instance.js';
 import { MiMeta } from '@/models/Meta.js';
 import { MiModerationLog } from '@/models/ModerationLog.js';
-import { MiMutedNote } from '@/models/MutedNote.js';
 import { MiMuting } from '@/models/Muting.js';
 import { MiRenoteMuting } from '@/models/RenoteMuting.js';
 import { MiNote } from '@/models/Note.js';
@@ -62,7 +61,7 @@ import { MiUserIp } from '@/models/UserIp.js';
 import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUserList } from '@/models/UserList.js';
 import { MiUserListFavorite } from '@/models/UserListFavorite.js';
-import { MiUserListJoining } from '@/models/UserListJoining.js';
+import { MiUserListMembership } from '@/models/UserListMembership.js';
 import { MiUserNotePining } from '@/models/UserNotePining.js';
 import { MiUserPending } from '@/models/UserPending.js';
 import { MiUserProfile } from '@/models/UserProfile.js';
@@ -138,7 +137,7 @@ export const entities = [
 	MiUserPublickey,
 	MiUserList,
 	MiUserListFavorite,
-	MiUserListJoining,
+	MiUserListMembership,
 	MiUserNotePining,
 	MiUserSecurityKey,
 	MiUsedUsername,
@@ -174,7 +173,6 @@ export const entities = [
 	MiPromoNote,
 	MiPromoRead,
 	MiRelay,
-	MiMutedNote,
 	MiChannel,
 	MiChannelFollowing,
 	MiChannelFavorite,
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index f0453f7054009c8437a59f4adc13290ed646c46c..e252c5d8a1444142d274558b00eb983831f86ed2 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -6,7 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In, LessThan } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, MutedNotesRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/_.js';
+import type { AntennasRepository, RoleAssignmentsRepository, UserIpsRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
@@ -25,9 +25,6 @@ export class CleanProcessorService {
 		@Inject(DI.userIpsRepository)
 		private userIpsRepository: UserIpsRepository,
 
-		@Inject(DI.mutedNotesRepository)
-		private mutedNotesRepository: MutedNotesRepository,
-
 		@Inject(DI.antennasRepository)
 		private antennasRepository: AntennasRepository,
 
@@ -48,16 +45,6 @@ export class CleanProcessorService {
 			createdAt: LessThan(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90))),
 		});
 
-		this.mutedNotesRepository.delete({
-			id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
-			reason: 'word',
-		});
-
-		this.mutedNotesRepository.delete({
-			id: LessThan(this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 90)))),
-			reason: 'word',
-		});
-
 		// 使われてないアンテナを停止
 		if (this.config.deactivateAntennaThreshold > 0) {
 			this.antennasRepository.update({
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index f941fb6e858cdb50b3a4b38cadad4c10317c55eb..a0afbee3baa1357604236e70d891863ce4d63a1d 100644
--- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { format as DateFormat } from 'date-fns';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AntennasRepository, UsersRepository, UserListJoiningsRepository, MiUser } from '@/models/_.js';
+import type { AntennasRepository, UsersRepository, UserListMembershipsRepository, MiUser } from '@/models/_.js';
 import Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { bindThis } from '@/decorators.js';
@@ -29,8 +29,8 @@ export class ExportAntennasProcessorService {
 		@Inject(DI.antennasRepository)
 		private antennsRepository: AntennasRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private driveService: DriveService,
 		private utilityService: UtilityService,
@@ -65,9 +65,9 @@ export class ExportAntennasProcessorService {
 			for (const [index, antenna] of antennas.entries()) {
 				let users: MiUser[] | undefined;
 				if (antenna.userListId !== null) {
-					const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId });
+					const memberships = await this.userListMembershipsRepository.findBy({ userListId: antenna.userListId });
 					users = await this.usersRepository.findBy({
-						id: In(joinings.map(j => j.userId)),
+						id: In(memberships.map(j => j.userId)),
 					});
 				}
 				write(JSON.stringify({
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index 7baaa7081a86724dd3b6219f3f685194b7f97fd1..a3f9441dc28247fc7ee649c1506d6dda615e89fd 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -8,7 +8,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { In } from 'typeorm';
 import { format as dateFormat } from 'date-fns';
 import { DI } from '@/di-symbols.js';
-import type { UserListJoiningsRepository, UserListsRepository, UsersRepository } from '@/models/_.js';
+import type { UserListMembershipsRepository, UserListsRepository, UsersRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
@@ -29,8 +29,8 @@ export class ExportUserListsProcessorService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private utilityService: UtilityService,
 		private driveService: DriveService,
@@ -61,9 +61,9 @@ export class ExportUserListsProcessorService {
 			const stream = fs.createWriteStream(path, { flags: 'a' });
 
 			for (const list of lists) {
-				const joinings = await this.userListJoiningsRepository.findBy({ userListId: list.id });
+				const memberships = await this.userListMembershipsRepository.findBy({ userListId: list.id });
 				const users = await this.usersRepository.findBy({
-					id: In(joinings.map(j => j.userId)),
+					id: In(memberships.map(j => j.userId)),
 				});
 
 				for (const u of users) {
diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
index 60a0d1605f11fd6233ab9c4ab329cd11fa7b40aa..9be36a9d0d1ed7b5b5537ae85d23ba4a7b834cc5 100644
--- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
@@ -6,7 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { UsersRepository, DriveFilesRepository, UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
+import type { UsersRepository, DriveFilesRepository, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
 import * as Acct from '@/misc/acct.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
@@ -33,8 +33,8 @@ export class ImportUserListsProcessorService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private utilityService: UtilityService,
 		private idService: IdService,
@@ -99,7 +99,7 @@ export class ImportUserListsProcessorService {
 					target = await this.remoteUserResolveService.resolveUser(username, host);
 				}
 
-				if (await this.userListJoiningsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
+				if (await this.userListMembershipsRepository.findOneBy({ userListId: list!.id, userId: target.id }) != null) continue;
 
 				this.userListService.addMember(target, list!, user);
 			} catch (e) {
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 2428fa279233cb418a55a7698f74daf7b961588e..a7f6f82daff66d4cbaf0bbbe1b74cbbb61344c66 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -379,9 +379,10 @@ export class ActivityPubServerService {
 		if (page) {
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), sinceId, untilId)
 				.andWhere('note.userId = :userId', { userId: user.id })
-				.andWhere(new Brackets(qb => { qb
-					.where('note.visibility = \'public\'')
-					.orWhere('note.visibility = \'home\'');
+				.andWhere(new Brackets(qb => {
+					qb
+						.where('note.visibility = \'public\'')
+						.orWhere('note.visibility = \'home\'');
 				}))
 				.andWhere('note.localOnly = FALSE');
 
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 79f130dabe4abac6242142a5b50e0454cbc08d6f..79b0a57f2b2c410cc9ab447027f388515b42287d 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -102,6 +102,8 @@ export class NodeinfoServerService {
 					},
 					langs: meta.langs,
 					tosUrl: meta.termsOfServiceUrl,
+					privacyPolicyUrl: meta.privacyPolicyUrl,
+					impressumUrl: meta.impressumUrl,
 					repositoryUrl: meta.repositoryUrl,
 					feedbackUrl: meta.feedbackUrl,
 					disableRegistration: meta.disableRegistration,
@@ -133,7 +135,11 @@ export class NodeinfoServerService {
 				.type(
 					'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"',
 				)
-				.header('Cache-Control', 'public, max-age=600');
+				.header('Cache-Control', 'public, max-age=600')
+				.header('Access-Control-Allow-Headers', 'Accept')
+				.header('Access-Control-Allow-Methods', 'GET, OPTIONS')
+				.header('Access-Control-Allow-Origin', '*')
+				.header('Access-Control-Expose-Headers', 'Vary');
 			return { version: '2.1', ...base };
 		});
 
@@ -146,7 +152,11 @@ export class NodeinfoServerService {
 				.type(
 					'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"',
 				)
-				.header('Cache-Control', 'public, max-age=600');
+				.header('Cache-Control', 'public, max-age=600')
+				.header('Access-Control-Allow-Headers', 'Accept')
+				.header('Access-Control-Allow-Methods', 'GET, OPTIONS')
+				.header('Access-Control-Allow-Origin', '*')
+				.header('Access-Control-Expose-Headers', 'Vary');
 			return { version: '2.0', ...base };
 		});
 
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 0e4a5ece3ec91fd00c686112f6e51689a32934bf..e598b91e51813b20b37e482fe0e0eaf5dd9cf51f 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -199,10 +199,10 @@ export class ServerService implements OnApplicationShutdown {
 					includeSecrets: true,
 				}));
 
-				reply.code(200);
-				return 'Verify succeeded!';
+				reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。');
+				return;
 			} else {
-				reply.code(404);
+				reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください');
 				return;
 			}
 		});
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index c883c96ba2d1d903896066f7b587e8ac6a8e7544..f834561456e17d6777527a2bc6d7d12456e784d9 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -205,7 +205,6 @@ import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
 import * as ep___i_favorites from './endpoints/i/favorites.js';
 import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
 import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
-import * as ep___i_getWordMutedNotesCount from './endpoints/i/get-word-muted-notes-count.js';
 import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
 import * as ep___i_importFollowing from './endpoints/i/import-following.js';
 import * as ep___i_importMuting from './endpoints/i/import-muting.js';
@@ -258,7 +257,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
 import * as ep___notes_conversation from './endpoints/notes/conversation.js';
 import * as ep___notes_create from './endpoints/notes/create.js';
 import * as ep___notes_delete from './endpoints/notes/delete.js';
-import * as ep___notes_update from './endpoints/notes/update.js';
 import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
 import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
 import * as ep___notes_featured from './endpoints/notes/featured.js';
@@ -327,6 +325,7 @@ import * as ep___users_followers from './endpoints/users/followers.js';
 import * as ep___users_following from './endpoints/users/following.js';
 import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
 import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
+import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
 import * as ep___users_lists_create from './endpoints/users/lists/create.js';
 import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
 import * as ep___users_lists_list from './endpoints/users/lists/list.js';
@@ -336,7 +335,9 @@ import * as ep___users_lists_show from './endpoints/users/lists/show.js';
 import * as ep___users_lists_update from './endpoints/users/lists/update.js';
 import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
 import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
+import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
+import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
+import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
 import * as ep___users_notes from './endpoints/users/notes.js';
 import * as ep___users_pages from './endpoints/users/pages.js';
 import * as ep___users_flashs from './endpoints/users/flashs.js';
@@ -554,7 +555,6 @@ const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass:
 const $i_favorites: Provider = { provide: 'ep:i/favorites', useClass: ep___i_favorites.default };
 const $i_gallery_likes: Provider = { provide: 'ep:i/gallery/likes', useClass: ep___i_gallery_likes.default };
 const $i_gallery_posts: Provider = { provide: 'ep:i/gallery/posts', useClass: ep___i_gallery_posts.default };
-const $i_getWordMutedNotesCount: Provider = { provide: 'ep:i/get-word-muted-notes-count', useClass: ep___i_getWordMutedNotesCount.default };
 const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass: ep___i_importBlocking.default };
 const $i_importFollowing: Provider = { provide: 'ep:i/import-following', useClass: ep___i_importFollowing.default };
 const $i_importMuting: Provider = { provide: 'ep:i/import-muting', useClass: ep___i_importMuting.default };
@@ -607,7 +607,6 @@ const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes
 const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
 const $notes_create: Provider = { provide: 'ep:notes/create', useClass: ep___notes_create.default };
 const $notes_delete: Provider = { provide: 'ep:notes/delete', useClass: ep___notes_delete.default };
-const $notes_update: Provider = { provide: 'ep:notes/update', useClass: ep___notes_update.default };
 const $notes_favorites_create: Provider = { provide: 'ep:notes/favorites/create', useClass: ep___notes_favorites_create.default };
 const $notes_favorites_delete: Provider = { provide: 'ep:notes/favorites/delete', useClass: ep___notes_favorites_delete.default };
 const $notes_featured: Provider = { provide: 'ep:notes/featured', useClass: ep___notes_featured.default };
@@ -676,6 +675,7 @@ const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep
 const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default };
 const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
 const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
+const $users_featuredNotes: Provider = { provide: 'ep:users/featured-notes', useClass: ep___users_featuredNotes.default };
 const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
 const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
 const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
@@ -685,7 +685,9 @@ const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass:
 const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
 const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
 const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
-const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default };
+const $users_lists_createFromPublic: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_createFromPublic.default };
+const $users_lists_updateMembership: Provider = { provide: 'ep:users/lists/update-membership', useClass: ep___users_lists_updateMembership.default };
+const $users_lists_getMemberships: Provider = { provide: 'ep:users/lists/get-memberships', useClass: ep___users_lists_getMemberships.default };
 const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
 const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
 const $users_flashs: Provider = { provide: 'ep:users/flashs', useClass: ep___users_flashs.default };
@@ -907,7 +909,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_favorites,
 		$i_gallery_likes,
 		$i_gallery_posts,
-		$i_getWordMutedNotesCount,
 		$i_importBlocking,
 		$i_importFollowing,
 		$i_importMuting,
@@ -960,7 +961,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$notes_conversation,
 		$notes_create,
 		$notes_delete,
-		$notes_update,
 		$notes_favorites_create,
 		$notes_favorites_delete,
 		$notes_featured,
@@ -1029,6 +1029,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_following,
 		$users_gallery_posts,
 		$users_getFrequentlyRepliedUsers,
+		$users_featuredNotes,
 		$users_lists_create,
 		$users_lists_delete,
 		$users_lists_list,
@@ -1038,7 +1039,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_lists_update,
 		$users_lists_favorite,
 		$users_lists_unfavorite,
-		$users_lists_create_from_public,
+		$users_lists_createFromPublic,
+		$users_lists_updateMembership,
+		$users_lists_getMemberships,
 		$users_notes,
 		$users_pages,
 		$users_flashs,
@@ -1254,7 +1257,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_favorites,
 		$i_gallery_likes,
 		$i_gallery_posts,
-		$i_getWordMutedNotesCount,
 		$i_importBlocking,
 		$i_importFollowing,
 		$i_importMuting,
@@ -1307,7 +1309,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$notes_conversation,
 		$notes_create,
 		$notes_delete,
-		$notes_update,
 		$notes_favorites_create,
 		$notes_favorites_delete,
 		$notes_featured,
@@ -1373,6 +1374,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_following,
 		$users_gallery_posts,
 		$users_getFrequentlyRepliedUsers,
+		$users_featuredNotes,
 		$users_lists_create,
 		$users_lists_delete,
 		$users_lists_list,
@@ -1382,7 +1384,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_lists_update,
 		$users_lists_favorite,
 		$users_lists_unfavorite,
-		$users_lists_create_from_public,
+		$users_lists_createFromPublic,
+		$users_lists_updateMembership,
+		$users_lists_getMemberships,
 		$users_notes,
 		$users_pages,
 		$users_flashs,
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 9acaa688c508c13a093870112a1e6f2c0b262dc1..badcec1b33f89c082286e662cc6473c443e84d90 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -14,6 +14,7 @@ import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { CacheService } from '@/core/CacheService.js';
 import { MiLocalUser } from '@/models/User.js';
+import { UserService } from '@/core/UserService.js';
 import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
 import MainStreamConnection from './stream/Connection.js';
 import { ChannelsService } from './stream/ChannelsService.js';
@@ -37,6 +38,7 @@ export class StreamingApiServerService {
 		private authenticateService: AuthenticateService,
 		private channelsService: ChannelsService,
 		private notificationService: NotificationService,
+		private usersService: UserService,
 	) {
 	}
 
@@ -130,14 +132,10 @@ export class StreamingApiServerService {
 			this.#connections.set(connection, Date.now());
 
 			const userUpdateIntervalId = user ? setInterval(() => {
-				this.usersRepository.update(user.id, {
-					lastActiveDate: new Date(),
-				});
+				this.usersService.updateLastActiveDate(user);
 			}, 1000 * 60 * 5) : null;
 			if (user) {
-				this.usersRepository.update(user.id, {
-					lastActiveDate: new Date(),
-				});
+				this.usersService.updateLastActiveDate(user);
 			}
 
 			connection.once('close', () => {
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index b40d654f9c8b5e55f3e148b7883d272a05f86529..d12a035afac2f80725e4447c257424c1adb3976e 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -205,7 +205,6 @@ import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
 import * as ep___i_favorites from './endpoints/i/favorites.js';
 import * as ep___i_gallery_likes from './endpoints/i/gallery/likes.js';
 import * as ep___i_gallery_posts from './endpoints/i/gallery/posts.js';
-import * as ep___i_getWordMutedNotesCount from './endpoints/i/get-word-muted-notes-count.js';
 import * as ep___i_importBlocking from './endpoints/i/import-blocking.js';
 import * as ep___i_importFollowing from './endpoints/i/import-following.js';
 import * as ep___i_importMuting from './endpoints/i/import-muting.js';
@@ -258,7 +257,6 @@ import * as ep___notes_clips from './endpoints/notes/clips.js';
 import * as ep___notes_conversation from './endpoints/notes/conversation.js';
 import * as ep___notes_create from './endpoints/notes/create.js';
 import * as ep___notes_delete from './endpoints/notes/delete.js';
-import * as ep___notes_update from './endpoints/notes/update.js';
 import * as ep___notes_favorites_create from './endpoints/notes/favorites/create.js';
 import * as ep___notes_favorites_delete from './endpoints/notes/favorites/delete.js';
 import * as ep___notes_featured from './endpoints/notes/featured.js';
@@ -327,6 +325,7 @@ import * as ep___users_followers from './endpoints/users/followers.js';
 import * as ep___users_following from './endpoints/users/following.js';
 import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
 import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
+import * as ep___users_featuredNotes from './endpoints/users/featured-notes.js';
 import * as ep___users_lists_create from './endpoints/users/lists/create.js';
 import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
 import * as ep___users_lists_list from './endpoints/users/lists/list.js';
@@ -335,8 +334,10 @@ import * as ep___users_lists_push from './endpoints/users/lists/push.js';
 import * as ep___users_lists_show from './endpoints/users/lists/show.js';
 import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
 import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
-import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
+import * as ep___users_lists_createFromPublic from './endpoints/users/lists/create-from-public.js';
 import * as ep___users_lists_update from './endpoints/users/lists/update.js';
+import * as ep___users_lists_updateMembership from './endpoints/users/lists/update-membership.js';
+import * as ep___users_lists_getMemberships from './endpoints/users/lists/get-memberships.js';
 import * as ep___users_notes from './endpoints/users/notes.js';
 import * as ep___users_pages from './endpoints/users/pages.js';
 import * as ep___users_flashs from './endpoints/users/flashs.js';
@@ -552,7 +553,6 @@ const eps = [
 	['i/favorites', ep___i_favorites],
 	['i/gallery/likes', ep___i_gallery_likes],
 	['i/gallery/posts', ep___i_gallery_posts],
-	['i/get-word-muted-notes-count', ep___i_getWordMutedNotesCount],
 	['i/import-blocking', ep___i_importBlocking],
 	['i/import-following', ep___i_importFollowing],
 	['i/import-muting', ep___i_importMuting],
@@ -605,7 +605,6 @@ const eps = [
 	['notes/conversation', ep___notes_conversation],
 	['notes/create', ep___notes_create],
 	['notes/delete', ep___notes_delete],
-	['notes/update', ep___notes_update],
 	['notes/favorites/create', ep___notes_favorites_create],
 	['notes/favorites/delete', ep___notes_favorites_delete],
 	['notes/featured', ep___notes_featured],
@@ -674,6 +673,7 @@ const eps = [
 	['users/following', ep___users_following],
 	['users/gallery/posts', ep___users_gallery_posts],
 	['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
+	['users/featured-notes', ep___users_featuredNotes],
 	['users/lists/create', ep___users_lists_create],
 	['users/lists/delete', ep___users_lists_delete],
 	['users/lists/list', ep___users_lists_list],
@@ -683,7 +683,9 @@ const eps = [
 	['users/lists/favorite', ep___users_lists_favorite],
 	['users/lists/unfavorite', ep___users_lists_unfavorite],
 	['users/lists/update', ep___users_lists_update],
-	['users/lists/create-from-public', ep___users_lists_create_from_public],
+	['users/lists/create-from-public', ep___users_lists_createFromPublic],
+	['users/lists/update-membership', ep___users_lists_updateMembership],
+	['users/lists/get-memberships', ep___users_lists_getMemberships],
 	['users/notes', ep___users_notes],
 	['users/pages', ep___users_pages],
 	['users/flashs', ep___users_flashs],
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 24d3a8a9439642934e0aa4b477c61bc5a4de250c..faab8ee608da61e9baa0ad8cb5dd5382d88dcfdd 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -23,6 +23,11 @@ export const meta = {
 			code: 'NO_SUCH_FILE',
 			id: 'fc46b5a4-6b92-4c33-ac66-b806659bb5cf',
 		},
+		duplicateName: {
+			message: 'Duplicate name.',
+			code: 'DUPLICATE_NAME',
+			id: 'f7a3462c-4e6e-4069-8421-b9bd4f4c3975',
+		},
 	},
 } as const;
 
@@ -64,6 +69,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		super(meta, paramDef, async (ps, me) => {
 			const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 			if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
+			const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
+			if (isDuplicate) throw new ApiError(meta.errors.duplicateName);
 
 			const emoji = await this.customEmojiService.add({
 				driveFile,
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index 2d69857408efe50bb79a6153edc5b73859c31f5e..04226d8953895d81e3ab1aa87293af79105be907 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -74,6 +74,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 				if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
 			}
+			const emoji = await this.customEmojiService.getEmojiById(ps.id);
+			if (emoji != null) {
+				if (ps.name !== emoji.name) {
+					const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
+					if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
+				}
+			} else {
+				throw new ApiError(meta.errors.noSuchEmoji);
+			}
 
 			await this.customEmojiService.update(ps.id, {
 				driveFile,
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index c3ba07cdd0862f7b51e74875067617f503d74b4d..5a74456ab08d0c3dde653adf0a2d259a1430b157 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -105,40 +105,32 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
-			userStarForReactionFallback: {
-				type: 'boolean',
-				optional: true, nullable: false,
-			},
 			pinnedUsers: {
 				type: 'array',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 				items: {
 					type: 'string',
-					optional: false, nullable: false,
 				},
 			},
 			hiddenTags: {
 				type: 'array',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 				items: {
 					type: 'string',
-					optional: false, nullable: false,
 				},
 			},
 			blockedHosts: {
 				type: 'array',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 				items: {
 					type: 'string',
-					optional: false, nullable: false,
 				},
 			},
 			sensitiveWords: {
 				type: 'array',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 				items: {
 					type: 'string',
-					optional: false, nullable: false,
 				},
 			},
 			preservedUsernames: {
@@ -146,129 +138,124 @@ export const meta = {
 				optional: false, nullable: false,
 				items: {
 					type: 'string',
-					optional: false, nullable: false,
 				},
 			},
 			hcaptchaSecretKey: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			recaptchaSecretKey: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			turnstileSecretKey: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			sensitiveMediaDetection: {
 				type: 'string',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			sensitiveMediaDetectionSensitivity: {
 				type: 'string',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			setSensitiveFlagAutomatically: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			enableSensitiveMediaDetectionForVideos: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			proxyAccountId: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 				format: 'id',
 			},
-			summaryProxy: {
-				type: 'string',
-				optional: true, nullable: true,
-			},
 			email: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			smtpSecure: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			smtpHost: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			smtpPort: {
 				type: 'number',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			smtpUser: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			smtpPass: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			swPrivateKey: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			useObjectStorage: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			objectStorageBaseUrl: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStorageBucket: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStoragePrefix: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStorageEndpoint: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStorageRegion: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStoragePort: {
 				type: 'number',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStorageAccessKey: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStorageSecretKey: {
 				type: 'string',
-				optional: true, nullable: true,
+				optional: false, nullable: true,
 			},
 			objectStorageUseSSL: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			objectStorageUseProxy: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			objectStorageSetPublicRead: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			enableIpLogging: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			enableActiveEmailValidation: {
 				type: 'boolean',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			enableChartsForRemoteUser: {
 				type: 'boolean',
@@ -288,12 +275,32 @@ export const meta = {
 			},
 			manifestJsonOverride: {
 				type: 'string',
-				optional: true, nullable: false,
+				optional: false, nullable: false,
 			},
 			policies: {
 				type: 'object',
 				optional: false, nullable: false,
 			},
+			perLocalUserUserTimelineCacheMax: {
+				type: 'number',
+				optional: false, nullable: false,
+			},
+			perRemoteUserUserTimelineCacheMax: {
+				type: 'number',
+				optional: false, nullable: false,
+			},
+			perUserHomeTimelineCacheMax: {
+				type: 'number',
+				optional: false, nullable: false,
+			},
+			perUserListTimelineCacheMax: {
+				type: 'number',
+				optional: false, nullable: false,
+			},
+			notesPerOneAd: {
+				type: 'number',
+				optional: false, nullable: false,
+			},
 		},
 	},
 } as const;
@@ -313,7 +320,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private metaService: MetaService,
 	) {
-		super(meta, paramDef, async (ps, me) => {
+		super(meta, paramDef, async () => {
 			const instance = await this.metaService.fetch(true);
 
 			return {
@@ -328,6 +335,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				tosUrl: instance.termsOfServiceUrl,
 				repositoryUrl: instance.repositoryUrl,
 				feedbackUrl: instance.feedbackUrl,
+				impressumUrl: instance.impressumUrl,
+				privacyPolicyUrl: instance.privacyPolicyUrl,
 				disableRegistration: instance.disableRegistration,
 				emailRequiredForSignup: instance.emailRequiredForSignup,
 				enableHcaptcha: instance.enableHcaptcha,
@@ -399,6 +408,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				enableIdenticonGeneration: instance.enableIdenticonGeneration,
 				policies: { ...DEFAULT_POLICIES, ...instance.policies },
 				manifestJsonOverride: instance.manifestJsonOverride,
+				perLocalUserUserTimelineCacheMax: instance.perLocalUserUserTimelineCacheMax,
+				perRemoteUserUserTimelineCacheMax: instance.perRemoteUserUserTimelineCacheMax,
+				perUserHomeTimelineCacheMax: instance.perUserHomeTimelineCacheMax,
+				perUserListTimelineCacheMax: instance.perUserListTimelineCacheMax,
+				notesPerOneAd: instance.notesPerOneAd,
 			};
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts
index b1772be7777eb522a0c8d3dd8a3a8449603c0848..ef5627bc9a712fc81c43d7b1059f8f617b8fb86d 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts
@@ -61,9 +61,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId)
 				.andWhere('assign.roleId = :roleId', { roleId: role.id })
-				.andWhere(new Brackets(qb => { qb
-					.where('assign.expiresAt IS NULL')
-					.orWhere('assign.expiresAt > :now', { now: new Date() });
+				.andWhere(new Brackets(qb => {
+					qb
+						.where('assign.expiresAt IS NULL')
+						.orWhere('assign.expiresAt > :now', { now: new Date() });
 				}))
 				.innerJoinAndSelect('assign.user', 'user');
 
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index 345459753283cf690b23df2e8e627d120bda88ea..0731413d0535ad89b5d84f30d7d8197ea6a6eb77 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -85,6 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				isModerator: isModerator,
 				isSilenced: isSilenced,
 				isSuspended: user.isSuspended,
+				isHibernated: user.isHibernated,
 				lastActiveDate: user.lastActiveDate,
 				moderationNote: profile.moderationNote ?? '',
 				signins,
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 ea6ebdd1fe1ab76c9b0b23a7a9b32b2de94ccde9..7db25e659f95d45ec80b168bc34aa1a5abdeea9b 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -86,6 +86,8 @@ export const paramDef = {
 		tosUrl: { type: 'string', nullable: true },
 		repositoryUrl: { type: 'string' },
 		feedbackUrl: { type: 'string' },
+		impressumUrl: { type: 'string' },
+		privacyPolicyUrl: { type: 'string' },
 		useObjectStorage: { type: 'boolean' },
 		objectStorageBaseUrl: { type: 'string', nullable: true },
 		objectStorageBucket: { type: 'string', nullable: true },
@@ -108,6 +110,11 @@ export const paramDef = {
 		serverRules: { type: 'array', items: { type: 'string' } },
 		preservedUsernames: { type: 'array', items: { type: 'string' } },
 		manifestJsonOverride: { type: 'string' },
+		perLocalUserUserTimelineCacheMax: { type: 'integer' },
+		perRemoteUserUserTimelineCacheMax: { type: 'integer' },
+		perUserHomeTimelineCacheMax: { type: 'integer' },
+		perUserListTimelineCacheMax: { type: 'integer' },
+		notesPerOneAd: { type: 'integer' },
 	},
 	required: [],
 } as const;
@@ -341,6 +348,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.feedbackUrl = ps.feedbackUrl;
 			}
 
+			if (ps.impressumUrl !== undefined) {
+				set.impressumUrl = ps.impressumUrl;
+			}
+
+			if (ps.privacyPolicyUrl !== undefined) {
+				set.privacyPolicyUrl = ps.privacyPolicyUrl;
+			}
+
 			if (ps.useObjectStorage !== undefined) {
 				set.useObjectStorage = ps.useObjectStorage;
 			}
@@ -441,6 +456,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.manifestJsonOverride = ps.manifestJsonOverride;
 			}
 
+			if (ps.perLocalUserUserTimelineCacheMax !== undefined) {
+				set.perLocalUserUserTimelineCacheMax = ps.perLocalUserUserTimelineCacheMax;
+			}
+
+			if (ps.perRemoteUserUserTimelineCacheMax !== undefined) {
+				set.perRemoteUserUserTimelineCacheMax = ps.perRemoteUserUserTimelineCacheMax;
+			}
+
+			if (ps.perUserHomeTimelineCacheMax !== undefined) {
+				set.perUserHomeTimelineCacheMax = ps.perUserHomeTimelineCacheMax;
+			}
+
+			if (ps.perUserListTimelineCacheMax !== undefined) {
+				set.perUserListTimelineCacheMax = ps.perUserListTimelineCacheMax;
+			}
+
+			if (ps.notesPerOneAd !== undefined) {
+				set.notesPerOneAd = ps.notesPerOneAd;
+			}
+
 			const before = await this.metaService.fetch(true);
 
 			await this.metaService.update(set);
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index eaae7bff62f1170e8f352ba878bd1180dcadc676..6d69971e30e78701980c69286f08c2f1f6b7d8ca 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -12,6 +12,7 @@ import { NoteReadService } from '@/core/NoteReadService.js';
 import { DI } from '@/di-symbols.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { IdService } from '@/core/IdService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -56,8 +57,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
 
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
@@ -69,8 +70,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
 		private noteReadService: NoteReadService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
 			const antenna = await this.antennasRepository.findOneBy({
 				id: ps.antennaId,
 				userId: me.id,
@@ -85,19 +90,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				lastUsedAt: new Date(),
 			});
 
-			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			const noteIdsRes = await this.redisClient.xrevrange(
-				`antennaTimeline:${antenna.id}`,
-				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
-				'COUNT', limit);
-
-			if (noteIdsRes.length === 0) {
-				return [];
-			}
-
-			const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
-
+			let noteIds = await this.redisTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId);
+			noteIds = noteIds.slice(0, ps.limit);
 			if (noteIds.length === 0) {
 				return [];
 			}
@@ -115,7 +109,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			this.queryService.generateBlockedUserQuery(query, me);
 
 			const notes = await query.getMany();
-			notes.sort((a, b) => a.id > b.id ? -1 : 1);
+			if (sinceId != null && untilId == null) {
+				notes.sort((a, b) => a.id < b.id ? -1 : 1);
+			} else {
+				notes.sort((a, b) => a.id > b.id ? -1 : 1);
+			}
 
 			if (notes.length > 0) {
 				this.noteReadService.read(me.id, notes);
diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts
index 65df45706b60d0419b0ce33a3686fddf701da2e3..9c78a948443f00e5a52b29c93fc226877cae5b1b 100644
--- a/packages/backend/src/server/api/endpoints/channels/search.ts
+++ b/packages/backend/src/server/api/endpoints/channels/search.ts
@@ -55,9 +55,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			if (ps.query !== '') {
 				if (ps.type === 'nameAndDescription') {
-					query.andWhere(new Brackets(qb => { qb
-						.where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
-						.orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
+					query.andWhere(new Brackets(qb => {
+						qb
+							.where('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
+							.orWhere('channel.description ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
 					}));
 				} else {
 					query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 026b6495372be586db9073c6435bcbc94616c091..2dfcf659d72b5a3af9d3e19323c23f2ff3a4cdb8 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -12,6 +12,9 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
 import { IdService } from '@/core/IdService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { CacheService } from '@/core/CacheService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -54,8 +57,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
 
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
@@ -66,9 +69,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private idService: IdService,
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
+		private redisTimelineService: RedisTimelineService,
+		private cacheService: CacheService,
 		private activeUsersChart: ActiveUsersChart,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+			const isRangeSpecified = untilId != null && sinceId != null;
+
 			const channel = await this.channelsRepository.findOneBy({
 				id: ps.channelId,
 			});
@@ -77,70 +86,66 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchChannel);
 			}
 
-			let timeline: MiNote[] = [];
+			if (me) this.activeUsersChart.read(me);
 
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			let noteIdsRes: [string, string[]][] = [];
+			if (isRangeSpecified || sinceId == null) {
+				const [
+					userIdsWhoMeMuting,
+				] = me ? await Promise.all([
+					this.cacheService.userMutingsCache.fetch(me.id),
+				]) : [new Set<string>()];
 
-			if (!ps.sinceId && !ps.sinceDate) {
-				noteIdsRes = await this.redisClient.xrevrange(
-					`channelTimeline:${channel.id}`,
-					ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-					'-',
-					'COUNT', limit);
-			}
+				let noteIds = await this.redisTimelineService.get(`channelTimeline:${channel.id}`, untilId, sinceId);
+				noteIds = noteIds.slice(0, ps.limit);
 
-			// redis から取得していないとき・取得数が足りないとき
-			if (noteIdsRes.length < limit) {
-				//#region Construct query
-				const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
-					.andWhere('note.channelId = :channelId', { channelId: channel.id })
-					.innerJoinAndSelect('note.user', 'user')
-					.leftJoinAndSelect('note.reply', 'reply')
-					.leftJoinAndSelect('note.renote', 'renote')
-					.leftJoinAndSelect('reply.user', 'replyUser')
-					.leftJoinAndSelect('renote.user', 'renoteUser')
-					.leftJoinAndSelect('note.channel', 'channel');
-
-				if (me) {
-					this.queryService.generateMutedUserQuery(query, me);
-					this.queryService.generateMutedNoteQuery(query, me);
-					this.queryService.generateBlockedUserQuery(query, me);
-				}
-				//#endregion
+				if (noteIds.length > 0) {
+					const query = this.notesRepository.createQueryBuilder('note')
+						.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+						.innerJoinAndSelect('note.user', 'user')
+						.leftJoinAndSelect('note.reply', 'reply')
+						.leftJoinAndSelect('note.renote', 'renote')
+						.leftJoinAndSelect('reply.user', 'replyUser')
+						.leftJoinAndSelect('renote.user', 'renoteUser')
+						.leftJoinAndSelect('note.channel', 'channel');
 
-				timeline = await query.limit(ps.limit).getMany();
-			} else {
-				const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId);
+					let timeline = await query.getMany();
 
-				if (noteIds.length === 0) {
-					return [];
-				}
+					timeline = timeline.filter(note => {
+						if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
 
-				//#region Construct query
-				const query = this.notesRepository.createQueryBuilder('note')
-					.where('note.id IN (:...noteIds)', { noteIds: noteIds })
-					.innerJoinAndSelect('note.user', 'user')
-					.leftJoinAndSelect('note.reply', 'reply')
-					.leftJoinAndSelect('note.renote', 'renote')
-					.leftJoinAndSelect('reply.user', 'replyUser')
-					.leftJoinAndSelect('renote.user', 'renoteUser')
-					.leftJoinAndSelect('note.channel', 'channel');
-
-				if (me) {
-					this.queryService.generateMutedUserQuery(query, me);
-					this.queryService.generateMutedNoteQuery(query, me);
-					this.queryService.generateBlockedUserQuery(query, me);
+						return true;
+					});
+
+					// TODO: フィルタで件数が減った場合の埋め合わせ処理
+
+					timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+
+					if (timeline.length > 0) {
+						return await this.noteEntityService.packMany(timeline, me);
+					}
 				}
-				//#endregion
+			}
 
-				timeline = await query.getMany();
-				timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+			//#region fallback to database
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+				.andWhere('note.channelId = :channelId', { channelId: channel.id })
+				.innerJoinAndSelect('note.user', 'user')
+				.leftJoinAndSelect('note.reply', 'reply')
+				.leftJoinAndSelect('note.renote', 'renote')
+				.leftJoinAndSelect('reply.user', 'replyUser')
+				.leftJoinAndSelect('renote.user', 'renoteUser')
+				.leftJoinAndSelect('note.channel', 'channel');
+
+			if (me) {
+				this.queryService.generateMutedUserQuery(query, me);
+				this.queryService.generateBlockedUserQuery(query, me);
 			}
+			//#endregion
 
-			if (me) this.activeUsersChart.read(me);
+			const timeline = await query.limit(ps.limit).getMany();
 
 			return await this.noteEntityService.packMany(timeline, me);
+			//#endregion
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
index 779231a856f1fb12469130e1a610d494e1ab82dc..14a13b09c926714b3d6978c4c983ec890a3eed61 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
@@ -6,6 +6,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { NotesRepository, DriveFilesRepository } from '@/models/_.js';
+import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../../error.js';
@@ -41,6 +42,9 @@ export const meta = {
 export const paramDef = {
 	type: 'object',
 	properties: {
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		fileId: { type: 'string', format: 'misskey:id' },
 	},
 	required: ['fileId'],
@@ -56,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private notesRepository: NotesRepository,
 
 		private noteEntityService: NoteEntityService,
+		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			// Fetch file
@@ -68,9 +73,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchFile);
 			}
 
-			const notes = await this.notesRepository.createQueryBuilder('note')
-				.where(':file = ANY(note.fileIds)', { file: file.id })
-				.getMany();
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
+			query.andWhere(':file = ANY(note.fileIds)', { file: file.id });
+
+			const notes = await query.limit(ps.limit).getMany();
 
 			return await this.noteEntityService.packMany(notes, me, {
 				detail: true,
diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts
index 25f393e5174504d521437f8df94629934f70b343..db17d151dfdd500fd639b11560124a54646eaa9d 100644
--- a/packages/backend/src/server/api/endpoints/following/update.ts
+++ b/packages/backend/src/server/api/endpoints/following/update.ts
@@ -57,8 +57,9 @@ export const paramDef = {
 	properties: {
 		userId: { type: 'string', format: 'misskey:id' },
 		notify: { type: 'string', enum: ['normal', 'none'] },
+		withReplies: { type: 'boolean' },
 	},
-	required: ['userId', 'notify'],
+	required: ['userId'],
 } as const;
 
 @Injectable()
@@ -98,7 +99,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			await this.followingsRepository.update({
 				id: exist.id,
 			}, {
-				notify: ps.notify === 'none' ? null : ps.notify,
+				notify: ps.notify != null ? (ps.notify === 'none' ? null : ps.notify) : undefined,
+				withReplies: ps.withReplies != null ? ps.withReplies : undefined,
 			});
 
 			return await this.userEntityService.pack(follower.id, me);
diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts
index 75d4fe3819b387de8b5c4719eef74c907c6ab2c3..8f382eb96b723e130c6194f506be7fdd3bb67d4b 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts
@@ -3,29 +3,11 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { NotesRepository } from '@/models/_.js';
-import type { MiNote } from '@/models/Note.js';
-import { safeForSql } from '@/misc/safe-for-sql.js';
-import { normalizeForSearch } from '@/misc/normalize-for-search.js';
-import { MetaService } from '@/core/MetaService.js';
 import { DI } from '@/di-symbols.js';
-
-/*
-トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
-ユニーク投稿数とはそのハッシュタグと投稿ユーザーのペアのカウントで、例えば同じユーザーが複数回同じハッシュタグを投稿してもそのハッシュタグのユニーク投稿数は1とカウントされる
-
-..が理想だけどPostgreSQLでどうするのか分からないので単に「直近Aの内に投稿されたユニーク投稿数が多いハッシュタグ」で妥協する
-*/
-
-const rangeA = 1000 * 60 * 60; // 60分
-//const rangeB = 1000 * 60 * 120; // 2時間
-//const coefficient = 1.25; // 「n倍」の部分
-//const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか
-
-const max = 5;
+import { FeaturedService } from '@/core/FeaturedService.js';
+import { HashtagService } from '@/core/HashtagService.js';
 
 export const meta = {
 	tags: ['hashtags'],
@@ -71,98 +53,18 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.notesRepository)
-		private notesRepository: NotesRepository,
-
-		private metaService: MetaService,
+		private featuredService: FeaturedService,
+		private hashtagService: HashtagService,
 	) {
 		super(meta, paramDef, async () => {
-			const instance = await this.metaService.fetch(true);
-			const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
-
-			const now = new Date(); // 5分単位で丸めた現在日時
-			now.setMinutes(Math.round(now.getMinutes() / 5) * 5, 0, 0);
-
-			const tagNotes = await this.notesRepository.createQueryBuilder('note')
-				.where('note.createdAt > :date', { date: new Date(now.getTime() - rangeA) })
-				.andWhere(new Brackets(qb => { qb
-					.where('note.visibility = \'public\'')
-					.orWhere('note.visibility = \'home\'');
-				}))
-				.andWhere('note.tags != \'{}\'')
-				.select(['note.tags', 'note.userId'])
-				.cache(60000) // 1 min
-				.getMany();
-
-			if (tagNotes.length === 0) {
-				return [];
-			}
-
-			const tags: {
-		name: string;
-		users: MiNote['userId'][];
-	}[] = [];
-
-			for (const note of tagNotes) {
-				for (const tag of note.tags) {
-					if (hiddenTags.includes(tag)) continue;
-
-					const x = tags.find(x => x.name === tag);
-					if (x) {
-						if (!x.users.includes(note.userId)) {
-							x.users.push(note.userId);
-						}
-					} else {
-						tags.push({
-							name: tag,
-							users: [note.userId],
-						});
-					}
-				}
-			}
-
-			// タグを人気順に並べ替え
-			const hots = tags
-				.sort((a, b) => b.users.length - a.users.length)
-				.map(tag => tag.name)
-				.slice(0, max);
-
-			//#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する
-			const countPromises: Promise<number[]>[] = [];
-
-			const range = 20;
-
-			// 10分
-			const interval = 1000 * 60 * 10;
-
-			for (let i = 0; i < range; i++) {
-				countPromises.push(Promise.all(hots.map(tag => this.notesRepository.createQueryBuilder('note')
-					.select('count(distinct note.userId)')
-					.where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`)
-					.andWhere('note.createdAt < :lt', { lt: new Date(now.getTime() - (interval * i)) })
-					.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - (interval * (i + 1))) })
-					.cache(60000) // 1 min
-					.getRawOne()
-					.then(x => parseInt(x.count, 10)),
-				)));
-			}
-
-			const countsLog = await Promise.all(countPromises);
-			//#endregion
+			const ranking = await this.featuredService.getHashtagsRanking(10);
 
-			const totalCounts = await Promise.all(hots.map(tag => this.notesRepository.createQueryBuilder('note')
-				.select('count(distinct note.userId)')
-				.where(`'{"${safeForSql(tag) ? tag : 'aichan_kawaii'}"}' <@ note.tags`)
-				.andWhere('note.createdAt > :gt', { gt: new Date(now.getTime() - rangeA) })
-				.cache(60000 * 60) // 60 min
-				.getRawOne()
-				.then(x => parseInt(x.count, 10)),
-			));
+			const charts = ranking.length === 0 ? {} : await this.hashtagService.getCharts(ranking, 20);
 
-			const stats = hots.map((tag, i) => ({
+			const stats = ranking.map((tag, i) => ({
 				tag,
-				chart: countsLog.map(counts => counts[i]),
-				usersCount: totalCounts[i],
+				chart: charts[tag],
+				usersCount: Math.max(...charts[tag]),
 			}));
 
 			return stats;
diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts
deleted file mode 100644
index d62bfbb3edb37a8c832caf8046851dbb355101a9..0000000000000000000000000000000000000000
--- a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import type { MutedNotesRepository } from '@/models/_.js';
-import { DI } from '@/di-symbols.js';
-
-export const meta = {
-	tags: ['account'],
-
-	requireCredential: true,
-
-	kind: 'read:account',
-
-	res: {
-		type: 'object',
-		optional: false, nullable: false,
-		properties: {
-			count: {
-				type: 'number',
-				optional: false, nullable: false,
-			},
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {},
-	required: [],
-} as const;
-
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
-	constructor(
-		@Inject(DI.mutedNotesRepository)
-		private mutedNotesRepository: MutedNotesRepository,
-	) {
-		super(meta, paramDef, async (ps, me) => {
-			return {
-				count: await this.mutedNotesRepository.countBy({
-					userId: me.id,
-					reason: 'word',
-				}),
-			};
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index fa6486ed18341ef6cf8fb96936eeee50f2cab94a..2727e4f093c1b3c6c5c6ca1d5d5b7a09c35f6589 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -181,6 +181,11 @@ export const meta = {
 					},
 				},
 			},
+			notesPerOneAd: {
+				type: 'number',
+				optional: false, nullable: false,
+				default: 0,
+			},
 			requireSetup: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -214,11 +219,11 @@ export const meta = {
 						type: 'boolean',
 						optional: false, nullable: false,
 					},
-					localTimeLine: {
+					localTimeline: {
 						type: 'boolean',
 						optional: false, nullable: false,
 					},
-					globalTimeLine: {
+					globalTimeline: {
 						type: 'boolean',
 						optional: false, nullable: false,
 					},
@@ -299,6 +304,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				tosUrl: instance.termsOfServiceUrl,
 				repositoryUrl: instance.repositoryUrl,
 				feedbackUrl: instance.feedbackUrl,
+				impressumUrl: instance.impressumUrl,
+				privacyPolicyUrl: instance.privacyPolicyUrl,
 				disableRegistration: instance.disableRegistration,
 				emailRequiredForSignup: instance.emailRequiredForSignup,
 				enableHcaptcha: instance.enableHcaptcha,
@@ -329,6 +336,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					imageUrl: ad.imageUrl,
 					dayOfWeek: ad.dayOfWeek,
 				})),
+				notesPerOneAd: instance.notesPerOneAd,
 				enableEmail: instance.enableEmail,
 				enableServiceWorker: instance.enableServiceWorker,
 
diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts
index 1a82a4b5d7a7f602cd41a1e41b74b5444212b64b..1e569d9806d9719465e722c40b1774d8c32163f3 100644
--- a/packages/backend/src/server/api/endpoints/notes/children.ts
+++ b/packages/backend/src/server/api/endpoints/notes/children.ts
@@ -49,16 +49,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
-				.andWhere(new Brackets(qb => { qb
-					.where('note.replyId = :noteId', { noteId: ps.noteId })
-					.orWhere(new Brackets(qb => { qb
-						.where('note.renoteId = :noteId', { noteId: ps.noteId })
-						.andWhere(new Brackets(qb => { qb
-							.where('note.text IS NOT NULL')
-							.orWhere('note.fileIds != \'{}\'')
-							.orWhere('note.hasPoll = TRUE');
+				.andWhere(new Brackets(qb => {
+					qb
+						.where('note.replyId = :noteId', { noteId: ps.noteId })
+						.orWhere(new Brackets(qb => {
+							qb
+								.where('note.renoteId = :noteId', { noteId: ps.noteId })
+								.andWhere(new Brackets(qb => {
+									qb
+										.where('note.text IS NOT NULL')
+										.orWhere('note.fileIds != \'{}\'')
+										.orWhere('note.hasPoll = TRUE');
+								}));
 						}));
-					}));
 				}))
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 37a0525e25dd134813a49d9d07f3c1f6ea06f366..3ae4ac044a82d70ff9153e6c2050919cab63e386 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -57,6 +57,12 @@ export const meta = {
 			id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a',
 		},
 
+		cannotRenoteDueToVisibility: {
+			message: 'You can not Renote due to target visibility.',
+			code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY',
+			id: 'be9529e9-fe72-4de0-ae43-0b363c4938af',
+		},
+
 		noSuchReplyTarget: {
 			message: 'No such reply target.',
 			code: 'NO_SUCH_REPLY_TARGET',
@@ -231,6 +237,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 						throw new ApiError(meta.errors.youHaveBeenBlocked);
 					}
 				}
+
+				if (renote.visibility === 'followers' && renote.userId !== me.id) {
+					// 他人のfollowers noteはreject
+					throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
+				} else if (renote.visibility === 'specified') {
+					// specified / direct noteはreject
+					throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
+				}
 			}
 
 			let reply: MiNote | null = null;
diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts
index 5283b0e0bc8e6c78d1ff5685b341bd8fc2b55fc4..c456874309c5bce9113c1807158c05af297f76ac 100644
--- a/packages/backend/src/server/api/endpoints/notes/featured.ts
+++ b/packages/backend/src/server/api/endpoints/notes/featured.ts
@@ -6,9 +6,9 @@
 import { Inject, Injectable } from '@nestjs/common';
 import type { NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -32,7 +32,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
-		offset: { type: 'integer', default: 0 },
+		untilId: { type: 'string', format: 'misskey:id' },
 		channelId: { type: 'string', nullable: true, format: 'misskey:id' },
 	},
 	required: [],
@@ -40,41 +40,53 @@ export const paramDef = {
 
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	private globalNotesRankingCache: string[] = [];
+	private globalNotesRankingCacheLastFetchedAt = 0;
+
 	constructor(
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		private noteEntityService: NoteEntityService,
-		private queryService: QueryService,
+		private featuredService: FeaturedService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
+			let noteIds: string[];
+			if (ps.channelId) {
+				noteIds = await this.featuredService.getInChannelNotesRanking(ps.channelId, 50);
+			} else {
+				if (this.globalNotesRankingCacheLastFetchedAt !== 0 && (Date.now() - this.globalNotesRankingCacheLastFetchedAt < 1000 * 60 * 30)) {
+					noteIds = this.globalNotesRankingCache;
+				} else {
+					noteIds = await this.featuredService.getGlobalNotesRanking(100);
+					this.globalNotesRankingCache = noteIds;
+					this.globalNotesRankingCacheLastFetchedAt = Date.now();
+				}
+			}
+
+			if (noteIds.length === 0) {
+				return [];
+			}
+
+			noteIds.sort((a, b) => a > b ? -1 : 1);
+			if (ps.untilId) {
+				noteIds = noteIds.filter(id => id < ps.untilId!);
+			}
+			noteIds = noteIds.slice(0, ps.limit);
 
 			const query = this.notesRepository.createQueryBuilder('note')
-				.addSelect('note.score')
-				.where('note.userHost IS NULL')
-				.andWhere('note.score > 0')
-				.andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) })
-				.andWhere('note.visibility = \'public\'')
+				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser');
-
-			if (ps.channelId) query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
-
-			if (me) this.queryService.generateMutedUserQuery(query, me);
-			if (me) this.queryService.generateBlockedUserQuery(query, me);
-
-			let notes = await query
-				.orderBy('note.score', 'DESC')
-				.limit(100)
-				.getMany();
+				.leftJoinAndSelect('renote.user', 'renoteUser')
+				.leftJoinAndSelect('note.channel', 'channel');
 
-			notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
+			const notes = await query.getMany();
+			notes.sort((a, b) => a.id > b.id ? -1 : 1);
 
-			notes = notes.slice(ps.offset, ps.offset + ps.limit);
+			// TODO: ミュート等考慮
 
 			return await this.noteEntityService.packMany(notes, me);
 		});
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 8784e86153773a48b571851de74ecb272f05b0f9..be7557c2137adde0cba4f0a0fc61f109987fb085 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -40,7 +40,6 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		withFiles: { type: 'boolean', default: false },
-		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -79,10 +78,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser');
 
-			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
 			if (me) {
 				this.queryService.generateMutedUserQuery(query, me);
-				this.queryService.generateMutedNoteQuery(query, me);
 				this.queryService.generateBlockedUserQuery(query, me);
 				this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
 			}
@@ -90,16 +87,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (ps.withFiles) {
 				query.andWhere('note.fileIds != \'{}\'');
 			}
-
-			if (ps.withRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere(new Brackets(qb => {
-						qb.orWhere('note.text IS NOT NULL');
-						qb.orWhere('note.fileIds != \'{}\'');
-					}));
-				}));
-			}
 			//#endregion
 
 			const timeline = await query.limit(ps.limit).getMany();
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 9bde5dee21a6badc46bc3bec731ef569703336c6..1b77285d47ccbf95c6aa95df2d6c0cecf58951a0 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -5,14 +5,17 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository, FollowingsRepository } from '@/models/_.js';
+import * as Redis from 'ioredis';
+import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { IdService } from '@/core/IdService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { CacheService } from '@/core/CacheService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -51,7 +54,6 @@ export const paramDef = {
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
-		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 	},
 	required: [],
@@ -60,97 +62,81 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-
 		private noteEntityService: NoteEntityService,
-		private queryService: QueryService,
 		private roleService: RoleService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
+		private cacheService: CacheService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
 			const policies = await this.roleService.getUserPolicies(me.id);
 			if (!policies.ltlAvailable) {
 				throw new ApiError(meta.errors.stlDisabled);
 			}
 
-			//#region Construct query
-			const followingQuery = this.followingsRepository.createQueryBuilder('following')
-				.select('following.followeeId')
-				.where('following.followerId = :followerId', { followerId: me.id });
-
-			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
-				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
-				.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
-				.andWhere(new Brackets(qb => {
-					qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id })
-						.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
-				}))
+			const [
+				userIdsWhoMeMuting,
+				userIdsWhoMeMutingRenotes,
+				userIdsWhoBlockingMe,
+			] = await Promise.all([
+				this.cacheService.userMutingsCache.fetch(me.id),
+				this.cacheService.renoteMutingsCache.fetch(me.id),
+				this.cacheService.userBlockedCache.fetch(me.id),
+			]);
+
+			const [htlNoteIds, ltlNoteIds] = await this.redisTimelineService.getMulti([
+				ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`,
+				ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline',
+			], untilId, sinceId);
+
+			let noteIds = Array.from(new Set([...htlNoteIds, ...ltlNoteIds]));
+			noteIds.sort((a, b) => a > b ? -1 : 1);
+			noteIds = noteIds.slice(0, ps.limit);
+
+			if (noteIds.length === 0) {
+				return [];
+			}
+
+			const query = this.notesRepository.createQueryBuilder('note')
+				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.setParameters(followingQuery.getParameters());
-
-			this.queryService.generateChannelQuery(query, me);
-			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
-			this.queryService.generateVisibilityQuery(query, me);
-			this.queryService.generateMutedUserQuery(query, me);
-			this.queryService.generateMutedNoteQuery(query, me);
-			this.queryService.generateBlockedUserQuery(query, me);
-			this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
-
-			if (ps.includeMyRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.userId != :meId', { meId: me.id });
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.includeRenotedMyNotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.includeLocalRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteUserHost IS NOT NULL');
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.withFiles) {
-				query.andWhere('note.fileIds != \'{}\'');
-			}
+				.leftJoinAndSelect('note.channel', 'channel');
+
+			let timeline = await query.getMany();
+
+			timeline = timeline.filter(note => {
+				if (note.userId === me.id) {
+					return true;
+				}
+				if (isUserRelated(note, userIdsWhoBlockingMe)) return false;
+				if (isUserRelated(note, userIdsWhoMeMuting)) return false;
+				if (note.renoteId) {
+					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
+						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
+						if (ps.withRenotes === false) return false;
+					}
+				}
+
+				return true;
+			});
 
-			if (ps.withRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere(new Brackets(qb => {
-						qb.orWhere('note.text IS NOT NULL');
-						qb.orWhere('note.fileIds != \'{}\'');
-					}));
-				}));
-			}
-			//#endregion
+			// TODO: フィルタした結果件数が足りなかった場合の対応
 
-			const timeline = await query.limit(ps.limit).getMany();
+			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
 
 			process.nextTick(() => {
 				this.activeUsersChart.read(me);
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 0fefddc51b02b972a9c469078958508d875af8d8..2357f32d5e9bb6f02aa624f581d6630c231aafa2 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -5,14 +5,17 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository } from '@/models/_.js';
+import * as Redis from 'ioredis';
+import type { MiNote, NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { IdService } from '@/core/IdService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -41,11 +44,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		withFiles: { type: 'boolean', default: false },
-		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
-		fileType: { type: 'array', items: {
-			type: 'string',
-		} },
 		excludeNsfw: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
@@ -59,71 +58,75 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		private noteEntityService: NoteEntityService,
-		private queryService: QueryService,
 		private roleService: RoleService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
+		private cacheService: CacheService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
 			const policies = await this.roleService.getUserPolicies(me ? me.id : null);
 			if (!policies.ltlAvailable) {
 				throw new ApiError(meta.errors.ltlDisabled);
 			}
 
-			//#region Construct query
-			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
-				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
-				.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
-				.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
+			const [
+				userIdsWhoMeMuting,
+				userIdsWhoMeMutingRenotes,
+				userIdsWhoBlockingMe,
+			] = me ? await Promise.all([
+				this.cacheService.userMutingsCache.fetch(me.id),
+				this.cacheService.renoteMutingsCache.fetch(me.id),
+				this.cacheService.userBlockedCache.fetch(me.id),
+			]) : [new Set<string>(), new Set<string>(), new Set<string>()];
+
+			let noteIds = await this.redisTimelineService.get(ps.withFiles ? 'localTimelineWithFiles' : 'localTimeline', untilId, sinceId);
+			noteIds = noteIds.slice(0, ps.limit);
+
+			if (noteIds.length === 0) {
+				return [];
+			}
+
+			const query = this.notesRepository.createQueryBuilder('note')
+				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser');
-
-			this.queryService.generateChannelQuery(query, me);
-			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
-			this.queryService.generateVisibilityQuery(query, me);
-			if (me) this.queryService.generateMutedUserQuery(query, me);
-			if (me) this.queryService.generateMutedNoteQuery(query, me);
-			if (me) this.queryService.generateBlockedUserQuery(query, me);
-			if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
-
-			if (ps.withFiles) {
-				query.andWhere('note.fileIds != \'{}\'');
-			}
+				.leftJoinAndSelect('renote.user', 'renoteUser')
+				.leftJoinAndSelect('note.channel', 'channel');
 
-			if (ps.fileType != null) {
-				query.andWhere('note.fileIds != \'{}\'');
-				query.andWhere(new Brackets(qb => {
-					for (const type of ps.fileType!) {
-						const i = ps.fileType!.indexOf(type);
-						qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
-					}
-				}));
+			let timeline = await query.getMany();
 
-				if (ps.excludeNsfw) {
-					query.andWhere('note.cw IS NULL');
-					query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
+			timeline = timeline.filter(note => {
+				if (me && (note.userId === me.id)) {
+					return true;
+				}
+				if (me && isUserRelated(note, userIdsWhoBlockingMe)) return false;
+				if (me && isUserRelated(note, userIdsWhoMeMuting)) return false;
+				if (note.renoteId) {
+					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
+						if (me && isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
+						if (ps.withRenotes === false) return false;
+					}
 				}
-			}
 
-			if (ps.withRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere(new Brackets(qb => {
-						qb.orWhere('note.text IS NOT NULL');
-						qb.orWhere('note.fileIds != \'{}\'');
-					}));
-				}));
-			}
-			//#endregion
+				return true;
+			});
+
+			// TODO: フィルタした結果件数が足りなかった場合の対応
 
-			const timeline = await query.limit(ps.limit).getMany();
+			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
 
 			process.nextTick(() => {
 				if (me) {
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index 65e7bd8cd5188879d94056d06d28eda4eef75554..6fab024d17a044bab79cf8c0c80e15dc6cab1546 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -59,9 +59,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.where('following.followerId = :followerId', { followerId: me.id });
 
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
-				.andWhere(new Brackets(qb => { qb
-					.where(`'{"${me.id}"}' <@ note.mentions`)
-					.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
+				.andWhere(new Brackets(qb => {
+					qb
+						.where(`'{"${me.id}"}' <@ note.mentions`)
+						.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
 				}))
 				// Avoid scanning primary key index
 				.orderBy('CONCAT(note.id)', 'DESC')
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
index 29190af62a2c77c4acae6e106369bca27c0bd64c..986201e95083afeb822cf379c5fed3fce4e9b011 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -57,9 +57,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.where('poll.userHost IS NULL')
 				.andWhere('poll.userId != :meId', { meId: me.id })
 				.andWhere('poll.noteVisibility = \'public\'')
-				.andWhere(new Brackets(qb => { qb
-					.where('poll.expiresAt IS NULL')
-					.orWhere('poll.expiresAt > :now', { now: new Date() });
+				.andWhere(new Brackets(qb => {
+					qb
+						.where('poll.expiresAt IS NULL')
+						.orWhere('poll.expiresAt > :now', { now: new Date() });
 				}));
 
 			//#region exclude arleady voted polls
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index 0d47cc17020f7a1b85cd31f551da86a556c4d2f1..760d52c9db0e89961caa9c730aed80db834bc1f5 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -5,13 +5,17 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository, FollowingsRepository } from '@/models/_.js';
+import * as Redis from 'ioredis';
+import type { NotesRepository, FollowingsRepository, MiNote } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
 import { IdService } from '@/core/IdService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 
 export const meta = {
 	tags: ['notes'],
@@ -41,7 +45,6 @@ export const paramDef = {
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
-		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 	},
 	required: [],
@@ -50,96 +53,74 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-
 		private noteEntityService: NoteEntityService,
-		private queryService: QueryService,
 		private activeUsersChart: ActiveUsersChart,
 		private idService: IdService,
+		private cacheService: CacheService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const followees = await this.followingsRepository.createQueryBuilder('following')
-				.select('following.followeeId')
-				.where('following.followerId = :followerId', { followerId: me.id })
-				.getMany();
-
-			//#region Construct query
-			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
-				ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
-				// パフォーマンス上の利点が無さそう?
-				//.andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
+			const [
+				followings,
+				userIdsWhoMeMuting,
+				userIdsWhoMeMutingRenotes,
+				userIdsWhoBlockingMe,
+			] = await Promise.all([
+				this.cacheService.userFollowingsCache.fetch(me.id),
+				this.cacheService.userMutingsCache.fetch(me.id),
+				this.cacheService.renoteMutingsCache.fetch(me.id),
+				this.cacheService.userBlockedCache.fetch(me.id),
+			]);
+
+			let noteIds = await this.redisTimelineService.get(ps.withFiles ? `homeTimelineWithFiles:${me.id}` : `homeTimeline:${me.id}`, untilId, sinceId);
+			noteIds = noteIds.slice(0, ps.limit);
+
+			if (noteIds.length === 0) {
+				return [];
+			}
+
+			const query = this.notesRepository.createQueryBuilder('note')
+				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser');
-
-			if (followees.length > 0) {
-				const meOrFolloweeIds = [me.id, ...followees.map(f => f.followeeId)];
-
-				query.andWhere('note.userId IN (:...meOrFolloweeIds)', { meOrFolloweeIds: meOrFolloweeIds });
-			} else {
-				query.andWhere('note.userId = :meId', { meId: me.id });
-			}
-
-			this.queryService.generateChannelQuery(query, me);
-			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
-			this.queryService.generateVisibilityQuery(query, me);
-			this.queryService.generateMutedUserQuery(query, me);
-			this.queryService.generateMutedNoteQuery(query, me);
-			this.queryService.generateBlockedUserQuery(query, me);
-			this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
-
-			if (ps.includeMyRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.userId != :meId', { meId: me.id });
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.includeRenotedMyNotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.includeLocalRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteUserHost IS NOT NULL');
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.withFiles) {
-				query.andWhere('note.fileIds != \'{}\'');
-			}
+				.leftJoinAndSelect('renote.user', 'renoteUser')
+				.leftJoinAndSelect('note.channel', 'channel');
+
+			let timeline = await query.getMany();
+
+			timeline = timeline.filter(note => {
+				if (note.userId === me.id) {
+					return true;
+				}
+				if (isUserRelated(note, userIdsWhoBlockingMe)) return false;
+				if (isUserRelated(note, userIdsWhoMeMuting)) return false;
+				if (note.renoteId) {
+					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
+						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
+						if (ps.withRenotes === false) return false;
+					}
+				}
+				if (note.reply && note.reply.visibility === 'followers') {
+					if (!Object.hasOwn(followings, note.reply.userId)) return false;
+				}
+
+				return true;
+			});
 
-			if (ps.withRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere(new Brackets(qb => {
-						qb.orWhere('note.text IS NOT NULL');
-						qb.orWhere('note.fileIds != \'{}\'');
-					}));
-				}));
-			}
-			//#endregion
+			// TODO: フィルタした結果件数が足りなかった場合の対応
 
-			const timeline = await query.limit(ps.limit).getMany();
+			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
 
 			process.nextTick(() => {
 				this.activeUsersChart.read(me);
diff --git a/packages/backend/src/server/api/endpoints/notes/update.ts b/packages/backend/src/server/api/endpoints/notes/update.ts
deleted file mode 100644
index cdf7f085e0f1fc5508a696be7c3a21a7fc6925b4..0000000000000000000000000000000000000000
--- a/packages/backend/src/server/api/endpoints/notes/update.ts
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import ms from 'ms';
-import { Inject, Injectable } from '@nestjs/common';
-import type { UsersRepository, NotesRepository } from '@/models/_.js';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { NoteDeleteService } from '@/core/NoteDeleteService.js';
-import { DI } from '@/di-symbols.js';
-import { GetterService } from '@/server/api/GetterService.js';
-import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { ApiError } from '../../error.js';
-
-export const meta = {
-	tags: ['notes'],
-
-	requireCredential: true,
-	requireRolePolicy: 'canEditNote',
-
-	kind: 'write:notes',
-
-	limit: {
-		duration: ms('1hour'),
-		max: 10,
-		minInterval: ms('1sec'),
-	},
-
-	errors: {
-		noSuchNote: {
-			message: 'No such note.',
-			code: 'NO_SUCH_NOTE',
-			id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		noteId: { type: 'string', format: 'misskey:id' },
-		text: {
-			type: 'string',
-			minLength: 1,
-			maxLength: MAX_NOTE_TEXT_LENGTH,
-			nullable: false,
-		},
-		cw: { type: 'string', nullable: true, maxLength: 100 },
-	},
-	required: ['noteId', 'text', 'cw'],
-} as const;
-
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
-	constructor(
-		@Inject(DI.usersRepository)
-		private usersRepository: UsersRepository,
-
-		@Inject(DI.notesRepository)
-		private notesRepository: NotesRepository,
-
-		private getterService: GetterService,
-		private globalEventService: GlobalEventService,
-	) {
-		super(meta, paramDef, async (ps, me) => {
-			const note = await this.getterService.getNote(ps.noteId).catch(err => {
-				if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
-				throw err;
-			});
-
-			if (note.userId !== me.id) {
-				throw new ApiError(meta.errors.noSuchNote);
-			}
-
-			await this.notesRepository.update({ id: note.id }, {
-				updatedAt: new Date(),
-				cw: ps.cw,
-				text: ps.text,
-			});
-
-			this.globalEventService.publishNoteStream(note.id, 'updated', {
-				cw: ps.cw,
-				text: ps.text,
-			});
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index c20274b2baf12073e1c6f1975a0435881c4ceddf..f7ee58264e2fae3e40010b892bf55eef96484a61 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -5,12 +5,17 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository, UserListsRepository, UserListJoiningsRepository } from '@/models/_.js';
+import * as Redis from 'ioredis';
+import type { NotesRepository, UserListsRepository, UserListMembershipsRepository, MiNote } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import ActiveUsersChart from '@/core/chart/charts/active-users.js';
 import { DI } from '@/di-symbols.js';
+import { CacheService } from '@/core/CacheService.js';
+import { IdService } from '@/core/IdService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -49,7 +54,6 @@ export const paramDef = {
 		includeMyRenotes: { type: 'boolean', default: true },
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
-		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
 		withFiles: {
 			type: 'boolean',
@@ -63,20 +67,25 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
-
 		private noteEntityService: NoteEntityService,
-		private queryService: QueryService,
 		private activeUsersChart: ActiveUsersChart,
+		private cacheService: CacheService,
+		private idService: IdService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
 			const list = await this.userListsRepository.findOneBy({
 				id: ps.listId,
 				userId: me.id,
@@ -86,72 +95,53 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchList);
 			}
 
-			//#region Construct query
-			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
-				.innerJoin(this.userListJoiningsRepository.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId')
+			const [
+				userIdsWhoMeMuting,
+				userIdsWhoMeMutingRenotes,
+				userIdsWhoBlockingMe,
+			] = await Promise.all([
+				this.cacheService.userMutingsCache.fetch(me.id),
+				this.cacheService.renoteMutingsCache.fetch(me.id),
+				this.cacheService.userBlockedCache.fetch(me.id),
+			]);
+
+			let noteIds = await this.redisTimelineService.get(ps.withFiles ? `userListTimelineWithFiles:${list.id}` : `userListTimeline:${list.id}`, untilId, sinceId);
+			noteIds = noteIds.slice(0, ps.limit);
+
+			if (noteIds.length === 0) {
+				return [];
+			}
+
+			const query = this.notesRepository.createQueryBuilder('note')
+				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser')
-				.andWhere('userListJoining.userListId = :userListId', { userListId: list.id });
-
-			this.queryService.generateVisibilityQuery(query, me);
-			this.queryService.generateMutedUserQuery(query, me);
-			this.queryService.generateMutedNoteQuery(query, me);
-			this.queryService.generateBlockedUserQuery(query, me);
-			this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
-
-			if (ps.includeMyRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.userId != :meId', { meId: me.id });
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.includeRenotedMyNotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteUserId != :meId', { meId: me.id });
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (ps.includeLocalRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteUserHost IS NOT NULL');
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere('note.text IS NOT NULL');
-					qb.orWhere('note.fileIds != \'{}\'');
-					qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
-				}));
-			}
-
-			if (!ps.withReplies) {
-				query.andWhere('note.replyId IS NULL');
-			}
-
-			if (ps.withRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere(new Brackets(qb => {
-						qb.orWhere('note.text IS NOT NULL');
-						qb.orWhere('note.fileIds != \'{}\'');
-					}));
-				}));
-			}
+				.leftJoinAndSelect('note.channel', 'channel');
+
+			let timeline = await query.getMany();
+
+			timeline = timeline.filter(note => {
+				if (note.userId === me.id) {
+					return true;
+				}
+				if (isUserRelated(note, userIdsWhoBlockingMe)) return false;
+				if (isUserRelated(note, userIdsWhoMeMuting)) return false;
+				if (note.renoteId) {
+					if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
+						if (isUserRelated(note, userIdsWhoMeMutingRenotes)) return false;
+						if (ps.withRenotes === false) return false;
+					}
+				}
+
+				return true;
+			});
 
-			if (ps.withFiles) {
-				query.andWhere('note.fileIds != \'{}\'');
-			}
-			//#endregion
+			// TODO: フィルタした結果件数が足りなかった場合の対応
 
-			const timeline = await query.limit(ps.limit).getMany();
+			timeline.sort((a, b) => a.id > b.id ? -1 : 1);
 
 			this.activeUsersChart.read(me);
 
diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
index 6dc35907e1f27a40a35e0336d8c30bab04ec6564..0db51abc5557c457d5d43801335b5a244b542590 100644
--- a/packages/backend/src/server/api/endpoints/roles/notes.ts
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -11,6 +11,7 @@ import { QueryService } from '@/core/QueryService.js';
 import { DI } from '@/di-symbols.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { IdService } from '@/core/IdService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -53,8 +54,8 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
 
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
@@ -65,8 +66,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private idService: IdService,
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+
 			const role = await this.rolesRepository.findOneBy({
 				id: ps.roleId,
 				isPublic: true,
@@ -78,18 +83,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (!role.isExplorable) {
 				return [];
 			}
-			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
-			const noteIdsRes = await this.redisClient.xrevrange(
-				`roleTimeline:${role.id}`,
-				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : ps.untilDate ?? '+',
-				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : ps.sinceDate ?? '-',
-				'COUNT', limit);
-
-			if (noteIdsRes.length === 0) {
-				return [];
-			}
 
-			const noteIds = noteIdsRes.map(x => x[1][1]).filter(x => x !== ps.untilId && x !== ps.sinceId);
+			let noteIds = await this.redisTimelineService.get(`roleTimeline:${role.id}`, untilId, sinceId);
+			noteIds = noteIds.slice(0, ps.limit);
 
 			if (noteIds.length === 0) {
 				return [];
diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts
index 37aac908b53c4a2f5aed5916623935343611ade9..caaa3735e99c83f059520f407ac1837c31e7c3a0 100644
--- a/packages/backend/src/server/api/endpoints/roles/users.ts
+++ b/packages/backend/src/server/api/endpoints/roles/users.ts
@@ -62,9 +62,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId)
 				.andWhere('assign.roleId = :roleId', { roleId: role.id })
-				.andWhere(new Brackets(qb => { qb
-					.where('assign.expiresAt IS NULL')
-					.orWhere('assign.expiresAt > :now', { now: new Date() });
+				.andWhere(new Brackets(qb => {
+					qb
+						.where('assign.expiresAt IS NULL')
+						.orWhere('assign.expiresAt > :now', { now: new Date() });
 				}))
 				.innerJoinAndSelect('assign.user', 'user');
 
diff --git a/packages/backend/src/server/api/endpoints/users/featured-notes.ts b/packages/backend/src/server/api/endpoints/users/featured-notes.ts
new file mode 100644
index 0000000000000000000000000000000000000000..dec0b7a122e854e2dfc02f426adeec3114e298ba
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/featured-notes.ts
@@ -0,0 +1,80 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { NotesRepository } from '@/models/_.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { FeaturedService } from '@/core/FeaturedService.js';
+
+export const meta = {
+	tags: ['notes'],
+
+	requireCredential: false,
+	allowGet: true,
+	cacheSec: 3600,
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'Note',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		untilId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['userId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
+		private noteEntityService: NoteEntityService,
+		private featuredService: FeaturedService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			let noteIds = await this.featuredService.getPerUserNotesRanking(ps.userId, 50);
+
+			noteIds.sort((a, b) => a > b ? -1 : 1);
+			if (ps.untilId) {
+				noteIds = noteIds.filter(id => id < ps.untilId!);
+			}
+			noteIds = noteIds.slice(0, ps.limit);
+
+			if (noteIds.length === 0) {
+				return [];
+			}
+
+			const query = this.notesRepository.createQueryBuilder('note')
+				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+				.innerJoinAndSelect('note.user', 'user')
+				.leftJoinAndSelect('note.reply', 'reply')
+				.leftJoinAndSelect('note.renote', 'renote')
+				.leftJoinAndSelect('reply.user', 'replyUser')
+				.leftJoinAndSelect('renote.user', 'renoteUser')
+				.leftJoinAndSelect('note.channel', 'channel');
+
+			const notes = await query.getMany();
+			notes.sort((a, b) => a.id > b.id ? -1 : 1);
+
+			// TODO: ミュート等考慮
+
+			return await this.noteEntityService.packMany(notes, me);
+		});
+	}
+}
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 eae55905d35f954eca084b6e372a37a3989709c9..f2f6c4303a686993db0a4e686612c4d14d3a4b16 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
@@ -4,7 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js';
+import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js';
 import { IdService } from '@/core/IdService.js';
 import type { MiUserList } from '@/models/UserList.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -76,8 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		@Inject(DI.blockingsRepository)
 		private blockingsRepository: BlockingsRepository,
@@ -110,7 +110,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				name: ps.name,
 			} as MiUserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
 
-			const users = (await this.userListJoiningsRepository.findBy({
+			const users = (await this.userListMembershipsRepository.findBy({
 				userListId: ps.listId,
 			})).map(x => x.userId);
 
@@ -132,7 +132,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					}
 				}
 
-				const exist = await this.userListJoiningsRepository.exist({
+				const exist = await this.userListMembershipsRepository.exist({
 					where: {
 						userListId: userList.id,
 						userId: currentUser.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ae8b4e9b8124f53fab60293495c94e999e4e8259
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
@@ -0,0 +1,79 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { UserListsRepository, UserListFavoritesRepository, UserListMembershipsRepository } from '@/models/_.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { QueryService } from '@/core/QueryService.js';
+import { ApiError } from '../../../error.js';
+
+export const meta = {
+	tags: ['lists', 'account'],
+
+	requireCredential: false,
+
+	kind: 'read:account',
+
+	errors: {
+		noSuchList: {
+			message: 'No such list.',
+			code: 'NO_SUCH_LIST',
+			id: '7bc05c21-1d7a-41ae-88f1-66820f4dc686',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+		forPublic: { type: 'boolean', default: false },
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId'],
+} as const;
+
+@Injectable() // eslint-disable-next-line import/no-default-export
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.userListsRepository)
+		private userListsRepository: UserListsRepository,
+
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
+
+		private userListEntityService: UserListEntityService,
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			// Fetch the list
+			const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? {
+				id: ps.listId,
+				userId: me.id,
+			} : {
+				id: ps.listId,
+				isPublic: true,
+			});
+
+			if (userList == null) {
+				throw new ApiError(meta.errors.noSuchList);
+			}
+
+			const query = this.queryService.makePaginationQuery(this.userListMembershipsRepository.createQueryBuilder('membership'), ps.sinceId, ps.untilId)
+				.andWhere('membership.userListId = :userListId', { userListId: userList.id })
+				.innerJoinAndSelect('membership.user', 'user');
+
+			const memberships = await query
+				.limit(ps.limit)
+				.getMany();
+
+			return this.userListEntityService.packMembershipsMany(memberships);
+		});
+	}
+}
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 72a6a7380d9beee1f15c2d838f2bfadcda0d7153..c4ceec575b92f880b1f78d1df999e4e3776582aa 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -5,7 +5,7 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
-import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/_.js';
+import type { UserListsRepository, UserListMembershipsRepository, BlockingsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { UserListService } from '@/core/UserListService.js';
@@ -76,8 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		@Inject(DI.blockingsRepository)
 		private blockingsRepository: BlockingsRepository,
@@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			const exist = await this.userListJoiningsRepository.exist({
+			const exist = await this.userListMembershipsRepository.exist({
 				where: {
 					userListId: userList.id,
 					userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b69465b940e84e8e70ab9e0bc96308da62ae23e5
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
@@ -0,0 +1,79 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { UserListsRepository } from '@/models/_.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { DI } from '@/di-symbols.js';
+import { UserListService } from '@/core/UserListService.js';
+import { ApiError } from '../../../error.js';
+
+export const meta = {
+	tags: ['lists', 'users'],
+
+	requireCredential: true,
+
+	prohibitMoved: true,
+
+	kind: 'write:account',
+
+	errors: {
+		noSuchList: {
+			message: 'No such list.',
+			code: 'NO_SUCH_LIST',
+			id: '7f44670e-ab16-43b8-b4c1-ccd2ee89cc02',
+		},
+
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '588e7f72-c744-4a61-b180-d354e912bda2',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+		userId: { type: 'string', format: 'misskey:id' },
+		withReplies: { type: 'boolean' },
+	},
+	required: ['listId', 'userId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.userListsRepository)
+		private userListsRepository: UserListsRepository,
+
+		private userListService: UserListService,
+		private getterService: GetterService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			// Fetch the list
+			const userList = await this.userListsRepository.findOneBy({
+				id: ps.listId,
+				userId: me.id,
+			});
+
+			if (userList == null) {
+				throw new ApiError(meta.errors.noSuchList);
+			}
+
+			// Fetch the user
+			const user = await this.getterService.getUser(ps.userId).catch(err => {
+				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+				throw err;
+			});
+
+			await this.userListService.updateMembership(user, userList, {
+				withReplies: ps.withReplies,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index e660a0bb25ab03fd07f0b21ea5f0d34adf80dc2b..dfef35986e6af426574fd3383340453d8b7bc86a 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -5,19 +5,21 @@
 
 import { Brackets } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
-import type { NotesRepository } from '@/models/_.js';
+import * as Redis from 'ioredis';
+import type { MiNote, NotesRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { DI } from '@/di-symbols.js';
-import { GetterService } from '@/server/api/GetterService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { IdService } from '@/core/IdService.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { QueryService } from '@/core/QueryService.js';
+import { RedisTimelineService } from '@/core/RedisTimelineService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
 	tags: ['users', 'notes'],
 
-	description: 'Show all notes that this user created.',
-
 	res: {
 		type: 'array',
 		optional: false, nullable: false,
@@ -43,6 +45,7 @@ export const paramDef = {
 		userId: { type: 'string', format: 'misskey:id' },
 		withReplies: { type: 'boolean', default: false },
 		withRenotes: { type: 'boolean', default: true },
+		withChannelNotes: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
 		untilId: { type: 'string', format: 'misskey:id' },
@@ -50,9 +53,6 @@ export const paramDef = {
 		untilDate: { type: 'integer' },
 		includeMyRenotes: { type: 'boolean', default: true },
 		withFiles: { type: 'boolean', default: false },
-		fileType: { type: 'array', items: {
-			type: 'string',
-		} },
 		excludeNsfw: { type: 'boolean', default: false },
 	},
 	required: ['userId'],
@@ -61,23 +61,88 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.redisForTimelines)
+		private redisForTimelines: Redis.Redis,
+
 		@Inject(DI.notesRepository)
 		private notesRepository: NotesRepository,
 
 		private noteEntityService: NoteEntityService,
 		private queryService: QueryService,
-		private getterService: GetterService,
+		private cacheService: CacheService,
+		private idService: IdService,
+		private redisTimelineService: RedisTimelineService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			// Lookup user
-			const user = await this.getterService.getUser(ps.userId).catch(err => {
-				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
-				throw err;
-			});
+			const untilId = ps.untilId ?? (ps.untilDate ? this.idService.genId(new Date(ps.untilDate!)) : null);
+			const sinceId = ps.sinceId ?? (ps.sinceDate ? this.idService.genId(new Date(ps.sinceDate!)) : null);
+			const isRangeSpecified = untilId != null && sinceId != null;
+			const isSelf = me && (me.id === ps.userId);
+
+			if (isRangeSpecified || sinceId == null) {
+				const [
+					userIdsWhoMeMuting,
+				] = me ? await Promise.all([
+					this.cacheService.userMutingsCache.fetch(me.id),
+				]) : [new Set<string>()];
+
+				const [noteIdsRes, repliesNoteIdsRes, channelNoteIdsRes] = await Promise.all([
+					this.redisTimelineService.get(ps.withFiles ? `userTimelineWithFiles:${ps.userId}` : `userTimeline:${ps.userId}`, untilId, sinceId),
+					ps.withReplies ? this.redisTimelineService.get(`userTimelineWithReplies:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
+					ps.withChannelNotes ? this.redisTimelineService.get(`userTimelineWithChannel:${ps.userId}`, untilId, sinceId) : Promise.resolve([]),
+				]);
+
+				let noteIds = Array.from(new Set([
+					...noteIdsRes,
+					...repliesNoteIdsRes,
+					...channelNoteIdsRes,
+				]));
+				noteIds.sort((a, b) => a > b ? -1 : 1);
+				noteIds = noteIds.slice(0, ps.limit);
+
+				if (noteIds.length > 0) {
+					const isFollowing = me && Object.hasOwn(await this.cacheService.userFollowingsCache.fetch(me.id), ps.userId);
+
+					const query = this.notesRepository.createQueryBuilder('note')
+						.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+						.innerJoinAndSelect('note.user', 'user')
+						.leftJoinAndSelect('note.reply', 'reply')
+						.leftJoinAndSelect('note.renote', 'renote')
+						.leftJoinAndSelect('reply.user', 'replyUser')
+						.leftJoinAndSelect('renote.user', 'renoteUser')
+						.leftJoinAndSelect('note.channel', 'channel');
+
+					let timeline = await query.getMany();
+
+					timeline = timeline.filter(note => {
+						if (me && isUserRelated(note, userIdsWhoMeMuting, true)) return false;
+
+						if (note.renoteId) {
+							if (note.text == null && note.fileIds.length === 0 && !note.hasPoll) {
+								if (ps.withRenotes === false) return false;
+							}
+						}
+
+						if (note.channel?.isSensitive && !isSelf) return false;
+						if (note.visibility === 'specified' && (!me || (me.id !== note.userId && !note.visibleUserIds.some(v => v === me.id)))) return false;
+						if (note.visibility === 'followers' && !isFollowing && !isSelf) return false;
+
+						return true;
+					});
+
+					// TODO: フィルタで件数が減った場合の埋め合わせ処理
+
+					timeline.sort((a, b) => a.id > b.id ? -1 : 1);
+
+					if (timeline.length > 0) {
+						return await this.noteEntityService.packMany(timeline, me);
+					}
+				}
+			}
 
-			//#region Construct query
+			//#region fallback to database
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
-				.andWhere('note.userId = :userId', { userId: user.id })
+				.andWhere('note.userId = :userId', { userId: ps.userId })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
@@ -85,14 +150,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser');
 
-			query.andWhere(new Brackets(qb => {
-				qb.orWhere('note.channelId IS NULL');
-				qb.orWhere('channel.isSensitive = false');
-			}));
+			if (!ps.withChannelNotes) {
+				query.andWhere('note.channelId IS NULL');
+			}
 
 			this.queryService.generateVisibilityQuery(query, me);
 			if (me) {
-				this.queryService.generateMutedUserQuery(query, me, user);
+				this.queryService.generateMutedUserQuery(query, me, { id: ps.userId });
 				this.queryService.generateBlockedUserQuery(query, me);
 			}
 
@@ -100,38 +164,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				query.andWhere('note.fileIds != \'{}\'');
 			}
 
-			if (ps.fileType != null) {
-				query.andWhere('note.fileIds != \'{}\'');
-				query.andWhere(new Brackets(qb => {
-					for (const type of ps.fileType!) {
-						const i = ps.fileType!.indexOf(type);
-						qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type });
-					}
-				}));
-
-				if (ps.excludeNsfw) {
-					query.andWhere('note.cw IS NULL');
-					query.andWhere('0 = (SELECT COUNT(*) FROM drive_file df WHERE df.id = ANY(note."fileIds") AND df."isSensitive" = TRUE)');
-				}
-			}
-
-			if (!ps.withReplies) {
-				query.andWhere('note.replyId IS NULL');
-			}
-
-			if (ps.withRenotes === false) {
-				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.renoteId IS NULL');
-					qb.orWhere(new Brackets(qb => {
-						qb.orWhere('note.text IS NOT NULL');
-						qb.orWhere('note.fileIds != \'{}\'');
-					}));
-				}));
-			}
-
 			if (ps.includeMyRenotes === false) {
 				query.andWhere(new Brackets(qb => {
-					qb.orWhere('note.userId != :userId', { userId: user.id });
+					qb.orWhere('note.userId != :userId', { userId: ps.userId });
 					qb.orWhere('note.renoteId IS NULL');
 					qb.orWhere('note.text IS NOT NULL');
 					qb.orWhere('note.fileIds != \'{}\'');
@@ -139,11 +174,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}));
 			}
 
-			//#endregion
-
 			const timeline = await query.limit(ps.limit).getMany();
 
 			return await this.noteEntityService.packMany(timeline, me);
+			//#endregion
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 74408cc64aab631075bbec34487134876055c252..4bf25d9fbb676a195f2d5bbd2eb90f10fbb01d1a 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -92,9 +92,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					.andWhere(`user.id IN (${ followingQuery.getQuery() })`)
 					.andWhere('user.id != :meId', { meId: me.id })
 					.andWhere('user.isSuspended = FALSE')
-					.andWhere(new Brackets(qb => { qb
-						.where('user.updatedAt IS NULL')
-						.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+					.andWhere(new Brackets(qb => {
+						qb
+							.where('user.updatedAt IS NULL')
+							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
 					}));
 
 				query.setParameters(followingQuery.getParameters());
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index aff5b98779b8c92ca0c196a9584df5f122531917..32b5c123722715029ce4ffdbf821ac82f1c99212 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -64,9 +64,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (isUsername) {
 				const usernameQuery = this.usersRepository.createQueryBuilder('user')
 					.where('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' })
-					.andWhere(new Brackets(qb => { qb
-						.where('user.updatedAt IS NULL')
-						.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+					.andWhere(new Brackets(qb => {
+						qb
+							.where('user.updatedAt IS NULL')
+							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
 					}))
 					.andWhere('user.isSuspended = FALSE');
 
@@ -91,9 +92,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 							qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' });
 						}
 					}))
-					.andWhere(new Brackets(qb => { qb
-						.where('user.updatedAt IS NULL')
-						.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+					.andWhere(new Brackets(qb => {
+						qb
+							.where('user.updatedAt IS NULL')
+							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
 					}))
 					.andWhere('user.isSuspended = FALSE');
 
@@ -122,9 +124,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 					const query = this.usersRepository.createQueryBuilder('user')
 						.where(`user.id IN (${ profQuery.getQuery() })`)
-						.andWhere(new Brackets(qb => { qb
-							.where('user.updatedAt IS NULL')
-							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
+						.andWhere(new Brackets(qb => {
+							qb
+								.where('user.updatedAt IS NULL')
+								.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
 						}))
 						.andWhere('user.isSuspended = FALSE')
 						.setParameters(profQuery.getParameters());
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index a73071ea9985c922da9645fbec676fd85275d5da..f981e63871d287f37b65ece3e278b8abe4331727 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -11,7 +11,7 @@ import type { NoteReadService } from '@/core/NoteReadService.js';
 import type { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { CacheService } from '@/core/CacheService.js';
-import { MiUserProfile } from '@/models/_.js';
+import { MiFollowing, MiUserProfile } from '@/models/_.js';
 import type { StreamEventEmitter, GlobalEvents } from '@/core/GlobalEventService.js';
 import type { ChannelsService } from './ChannelsService.js';
 import type { EventEmitter } from 'events';
@@ -30,7 +30,7 @@ export default class Connection {
 	private subscribingNotes: any = {};
 	private cachedNotes: Packed<'Note'>[] = [];
 	public userProfile: MiUserProfile | null = null;
-	public following: Set<string> = new Set();
+	public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
 	public followingChannels: Set<string> = new Set();
 	public userIdsWhoMeMuting: Set<string> = new Set();
 	public userIdsWhoBlockingMe: Set<string> = new Set();
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index fef52b68561e6a9545cda6a28aeef9810934d954..03f2dff62b7b5eb7fc8423cdd3a43de12a23b9ec 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -16,10 +16,10 @@ import Channel from '../channel.js';
 
 class GlobalTimelineChannel extends Channel {
 	public readonly chName = 'globalTimeline';
-	public static shouldShare = true;
+	public static shouldShare = false;
 	public static requireCredential = false;
-	private withReplies: boolean;
 	private withRenotes: boolean;
+	private withFiles: boolean;
 
 	constructor(
 		private metaService: MetaService,
@@ -38,8 +38,8 @@ class GlobalTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.gtlAvailable) return;
 
-		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
+		this.withFiles = params.withFiles ?? false;
 
 		// Subscribe events
 		this.subscriber.on('notesStream', this.onNote);
@@ -47,6 +47,8 @@ class GlobalTimelineChannel extends Channel {
 
 	@bindThis
 	private async onNote(note: Packed<'Note'>) {
+		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
+
 		if (note.visibility !== 'public') return;
 		if (note.channelId != null) return;
 
@@ -64,7 +66,7 @@ class GlobalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && !this.withReplies) {
+		if (note.reply && !this.following[note.userId]?.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
@@ -82,13 +84,6 @@ class GlobalTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
-		// 流れてきたNoteがミュートすべきNoteだったら無視する
-		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
-		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
-		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
-		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
-
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 198c68e1c2584cf7a00be088d3ec445ed3e47b43..24be59050492ae96c405278e03699b6fb8331ad1 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -14,10 +14,10 @@ import Channel from '../channel.js';
 
 class HomeTimelineChannel extends Channel {
 	public readonly chName = 'homeTimeline';
-	public static shouldShare = true;
+	public static shouldShare = false;
 	public static requireCredential = true;
-	private withReplies: boolean;
 	private withRenotes: boolean;
+	private withFiles: boolean;
 
 	constructor(
 		private noteEntityService: NoteEntityService,
@@ -31,19 +31,21 @@ class HomeTimelineChannel extends Channel {
 
 	@bindThis
 	public async init(params: any) {
-		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
+		this.withFiles = params.withFiles ?? false;
 
 		this.subscriber.on('notesStream', this.onNote);
 	}
 
 	@bindThis
 	private async onNote(note: Packed<'Note'>) {
+		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
+
 		if (note.channelId) {
 			if (!this.followingChannels.has(note.channelId)) return;
 		} else {
 			// その投稿のユーザーをフォローしていなかったら弾く
-			if ((this.user!.id !== note.userId) && !this.following.has(note.userId)) return;
+			if ((this.user!.id !== note.userId) && !Object.hasOwn(this.following, note.userId)) return;
 		}
 
 		// Ignore notes from instances the user has muted
@@ -73,7 +75,7 @@ class HomeTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && !this.withReplies) {
+		if (note.reply && !this.following[note.userId]?.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
@@ -88,13 +90,6 @@ class HomeTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
-		// 流れてきたNoteがミュートすべきNoteだったら無視する
-		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
-		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
-		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
-		if (await checkWordMute(note, this.user, this.userProfile!.mutedWords)) return;
-
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index cde4297478e26c62baaab6682dce70162039cd55..d5f5d54e46e01ab16bdcedb4c0657fa95faf680f 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -16,10 +16,10 @@ import Channel from '../channel.js';
 
 class HybridTimelineChannel extends Channel {
 	public readonly chName = 'hybridTimeline';
-	public static shouldShare = true;
+	public static shouldShare = false;
 	public static requireCredential = true;
-	private withReplies: boolean;
 	private withRenotes: boolean;
+	private withFiles: boolean;
 
 	constructor(
 		private metaService: MetaService,
@@ -38,8 +38,8 @@ class HybridTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.ltlAvailable) return;
 
-		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
+		this.withFiles = params.withFiles ?? false;
 
 		// Subscribe events
 		this.subscriber.on('notesStream', this.onNote);
@@ -47,13 +47,15 @@ class HybridTimelineChannel extends Channel {
 
 	@bindThis
 	private async onNote(note: Packed<'Note'>) {
+		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
+
 		// チャンネルの投稿ではなく、自分自身の投稿 または
 		// チャンネルの投稿ではなく、その投稿のユーザーをフォローしている または
 		// チャンネルの投稿ではなく、全体公開のローカルの投稿 または
 		// フォローしているチャンネルの投稿 の場合だけ
 		if (!(
 			(note.channelId == null && this.user!.id === note.userId) ||
-			(note.channelId == null && this.following.has(note.userId)) ||
+			(note.channelId == null && Object.hasOwn(this.following, note.userId)) ||
 			(note.channelId == null && (note.user.host == null && note.visibility === 'public')) ||
 			(note.channelId != null && this.followingChannels.has(note.channelId))
 		)) return;
@@ -85,7 +87,7 @@ class HybridTimelineChannel extends Channel {
 		if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
 
 		// 関係ない返信は除外
-		if (note.reply && !this.withReplies) {
+		if (note.reply && !this.following[note.userId]?.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
@@ -100,13 +102,6 @@ class HybridTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
-		// 流れてきたNoteがミュートすべきNoteだったら無視する
-		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
-		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
-		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
-		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
-
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index ef708c4fee86fe4f922387973e5393d3e69d65a3..94c22f8915afeb7b0a42c4cf0e4994905d885cce 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -15,10 +15,10 @@ import Channel from '../channel.js';
 
 class LocalTimelineChannel extends Channel {
 	public readonly chName = 'localTimeline';
-	public static shouldShare = true;
+	public static shouldShare = false;
 	public static requireCredential = false;
-	private withReplies: boolean;
 	private withRenotes: boolean;
+	private withFiles: boolean;
 
 	constructor(
 		private metaService: MetaService,
@@ -37,8 +37,8 @@ class LocalTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.ltlAvailable) return;
 
-		this.withReplies = params.withReplies ?? false;
 		this.withRenotes = params.withRenotes ?? true;
+		this.withFiles = params.withFiles ?? false;
 
 		// Subscribe events
 		this.subscriber.on('notesStream', this.onNote);
@@ -46,6 +46,8 @@ class LocalTimelineChannel extends Channel {
 
 	@bindThis
 	private async onNote(note: Packed<'Note'>) {
+		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
+
 		if (note.user.host !== null) return;
 		if (note.visibility !== 'public') return;
 		if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
@@ -64,7 +66,7 @@ class LocalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && this.user && !this.withReplies) {
+		if (note.reply && this.user && !this.following[note.userId]?.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
@@ -79,13 +81,6 @@ class LocalTimelineChannel extends Channel {
 
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
-		// 流れてきたNoteがミュートすべきNoteだったら無視する
-		// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
-		// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
-		// レコードが追加されるNoteでも追加されるより先にここのストリーミングの処理に到達することが起こる。
-		// そのためレコードが存在するかのチェックでは不十分なので、改めてcheckWordMuteを呼んでいる
-		if (this.userProfile && await checkWordMute(note, this.user, this.userProfile.mutedWords)) return;
-
 		this.connection.cacheNote(note);
 
 		this.send('note', note);
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 8bbba0b6dbb917c5106f1309a9d5a56730b86434..240822d9ab559fdac7fee96220a3672833e25f64 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -4,7 +4,7 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import type { UserListJoiningsRepository, UserListsRepository } from '@/models/_.js';
+import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js';
 import type { MiUser } from '@/models/User.js';
 import { isUserRelated } from '@/misc/is-user-related.js';
 import type { Packed } from '@/misc/json-schema.js';
@@ -18,12 +18,13 @@ class UserListChannel extends Channel {
 	public static shouldShare = false;
 	public static requireCredential = false;
 	private listId: string;
-	public listUsers: MiUser['id'][] = [];
+	private membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
 	private listUsersClock: NodeJS.Timeout;
+	private withFiles: boolean;
 
 	constructor(
 		private userListsRepository: UserListsRepository,
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		private userListMembershipsRepository: UserListMembershipsRepository,
 		private noteEntityService: NoteEntityService,
 
 		id: string,
@@ -37,6 +38,7 @@ class UserListChannel extends Channel {
 	@bindThis
 	public async init(params: any) {
 		this.listId = params.listId as string;
+		this.withFiles = params.withFiles ?? false;
 
 		// Check existence and owner
 		const listExist = await this.userListsRepository.exist({
@@ -58,19 +60,27 @@ class UserListChannel extends Channel {
 
 	@bindThis
 	private async updateListUsers() {
-		const users = await this.userListJoiningsRepository.find({
+		const memberships = await this.userListMembershipsRepository.find({
 			where: {
 				userListId: this.listId,
 			},
 			select: ['userId'],
 		});
 
-		this.listUsers = users.map(x => x.userId);
+		const membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
+		for (const membership of memberships) {
+			membershipsMap[membership.userId] = {
+				withReplies: membership.withReplies,
+			};
+		}
+		this.membershipsMap = membershipsMap;
 	}
 
 	@bindThis
 	private async onNote(note: Packed<'Note'>) {
-		if (!this.listUsers.includes(note.userId)) return;
+		if (this.withFiles && (note.fileIds == null || note.fileIds.length === 0)) return;
+
+		if (!Object.hasOwn(this.membershipsMap, note.userId)) return;
 
 		if (['followers', 'specified'].includes(note.visibility)) {
 			note = await this.noteEntityService.pack(note.id, this.user, {
@@ -95,6 +105,13 @@ class UserListChannel extends Channel {
 			}
 		}
 
+		// 関係ない返信は除外
+		if (note.reply && !this.membershipsMap[note.userId]?.withReplies) {
+			const reply = note.reply;
+			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
+			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
+		}
+
 		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
@@ -124,8 +141,8 @@ export class UserListChannelService {
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
-		@Inject(DI.userListJoiningsRepository)
-		private userListJoiningsRepository: UserListJoiningsRepository,
+		@Inject(DI.userListMembershipsRepository)
+		private userListMembershipsRepository: UserListMembershipsRepository,
 
 		private noteEntityService: NoteEntityService,
 	) {
@@ -135,7 +152,7 @@ export class UserListChannelService {
 	public create(id: string, connection: Channel['connection']): UserListChannel {
 		return new UserListChannel(
 			this.userListsRepository,
-			this.userListJoiningsRepository,
+			this.userListMembershipsRepository,
 			this.noteEntityService,
 			id,
 			connection,
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 5c13c2c8701b8405b22527b63ab56f1d1a83f797..cf621f457936f9cf5533475ed96cfb0f62fa1733 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -188,7 +188,7 @@ export class ClientServerService {
 		// Authenticate
 		fastify.addHook('onRequest', async (request, reply) => {
 			// %71ueueとかでリクエストされたら困るため
-			const url = decodeURI(request.routerPath);
+			const url = decodeURI(request.routeOptions.url);
 			if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
 				const token = request.cookies.token;
 				if (token == null) {
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index a9b9a55bc085c79140584ecec319e7fdb62ed092..316073c992b10a1d0ef616394fa7f0bfb2d4ba56 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -171,6 +171,9 @@ export type ModerationLogPayloads = {
 	deleteUserAnnouncement: {
 		announcementId: string;
 		announcement: any;
+		userId: string;
+		userUsername: string;
+		userHost: string | null;
 	};
 	resetPassword: {
 		userId: string;
diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts
index c9e1ccc30410700e9740c9997d38ed3c77bc5735..7d57ba17b6fbd386f8ab6f4691af36b202638e39 100644
--- a/packages/backend/test/e2e/renote-mute.ts
+++ b/packages/backend/test/e2e/renote-mute.ts
@@ -6,7 +6,7 @@
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire } from '../utils.js';
+import { signup, api, post, react, startServer, waitFire, sleep } from '../utils.js';
 import type { INestApplicationContext } from '@nestjs/common';
 import type * as misskey from 'misskey-js';
 
@@ -42,6 +42,9 @@ describe('Renote Mute', () => {
 		const carolRenote = await post(carol, { renoteId: bobNote.id });
 		const carolNote = await post(carol, { text: 'hi' });
 
+		// redisに追加されるのを待つ
+		await sleep(100);
+
 		const res = await api('/notes/local-timeline', {}, alice);
 
 		assert.strictEqual(res.status, 200);
@@ -56,6 +59,9 @@ describe('Renote Mute', () => {
 		const carolRenote = await post(carol, { renoteId: bobNote.id, text: 'kore' });
 		const carolNote = await post(carol, { text: 'hi' });
 
+		// redisに追加されるのを待つ
+		await sleep(100);
+
 		const res = await api('/notes/local-timeline', {}, alice);
 
 		assert.strictEqual(res.status, 200);
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
new file mode 100644
index 0000000000000000000000000000000000000000..05209c902443e941d11c5ad058937c5c7afaac69
--- /dev/null
+++ b/packages/backend/test/e2e/timelines.ts
@@ -0,0 +1,1097 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+// How to run:
+// pnpm jest -- e2e/timelines.ts
+
+process.env.NODE_ENV = 'test';
+process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true';
+
+import * as assert from 'assert';
+import { signup, api, post, react, startServer, waitFire, sleep, uploadUrl, randomString } from '../utils.js';
+import type { INestApplicationContext } from '@nestjs/common';
+import type * as misskey from 'misskey-js';
+
+function genHost() {
+	return randomString() + '.example.com';
+}
+
+function waitForPushToTl() {
+	return sleep(300);
+}
+
+let app: INestApplicationContext;
+
+beforeAll(async () => {
+	app = await startServer();
+}, 1000 * 60 * 2);
+
+afterAll(async () => {
+	await app.close();
+});
+
+describe('Timelines', () => {
+	describe('Home TL', () => {
+		test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
+			const [alice] = await Promise.all([signup()]);
+
+			const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
+		});
+
+		test.concurrent('フォローしているユーザーのノートが含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi' });
+			const carolNote = await post(carol, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+			const carolNote = await post(carol, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: false でフォローしているユーザーの他人への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーの他人への返信が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーの他人へのDM返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーの他人の visibility: followers な投稿への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの visibility: followers な投稿への返信が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/create', { userId: carol.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi', visibility: 'followers' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === carolNote.id).text, 'hi');
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーの行った別のフォローしているユーザーの投稿への visibility: specified な返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/create', { userId: carol.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified', visibleUserIds: [carolNote.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true);
+		});
+
+		test.concurrent('withReplies: false でフォローしているユーザーのそのユーザー自身への返信が含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		});
+
+		test.concurrent('自分の他人への返信が含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi' });
+			const aliceNote = await post(alice, { text: 'hi', replyId: bobNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+		});
+
+		test.concurrent('フォローしているユーザーの他人の投稿のリノートが含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { renoteId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿のリノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { renoteId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {
+				withRenotes: false,
+			}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('[withRenotes: false] フォローしているユーザーの他人の投稿の引用が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {
+				withRenotes: false,
+			}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('フォローしているユーザーの他人への visibility: specified なノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/mute/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await api('/mute/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('[withFiles: true] フォローしているユーザーのファイル付きノートのみ含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const [bobFile, carolFile] = await Promise.all([
+				uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
+				uploadUrl(carol, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png'),
+			]);
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { fileIds: [bobFile.id] });
+			const carolNote1 = await post(carol, { text: 'hi' });
+			const carolNote2 = await post(carol, { fileIds: [carolFile.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', { withFiles: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote1.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote2.id), false);
+		}, 1000 * 10);
+
+		test.concurrent('フォローしているユーザーのチャンネル投稿が含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('自分の visibility: specified なノートが含まれる', async () => {
+			const [alice] = await Promise.all([signup()]);
+
+			const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
+		});
+
+		test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
+		});
+
+		test.concurrent('フォローしていないユーザーの自身を visibleUserIds に指定した visibility: specified なノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('フォローしているユーザーの自身を visibleUserIds に指定していない visibility: specified なノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('フォローしていないユーザーからの visibility: specified なノートに返信したときの自身のノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+			const aliceNote = await post(alice, { text: 'ok', visibility: 'specified', visibleUserIds: [bob.id], replyId: bobNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'ok');
+		});
+
+		/* TODO
+		test.concurrent('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] });
+			const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'ok');
+		});
+		*/
+
+		// ↑の挙動が理想だけど実装が面倒かも
+		test.concurrent('自身の visibility: specified なノートへのフォローしていないユーザーからの返信が含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const aliceNote = await post(alice, { text: 'hi', visibility: 'specified', visibleUserIds: [bob.id] });
+			const bobNote = await post(bob, { text: 'ok', visibility: 'specified', visibleUserIds: [alice.id], replyId: aliceNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+	});
+
+	describe('Local TL', () => {
+		test.concurrent('visibility: home なノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('チャンネル投稿が含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('リモートユーザーのノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		// 含まれても良いと思うけど実装が面倒なので含まれない
+		test.concurrent('フォローしているユーザーの visibility: home なノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi', visibility: 'home' });
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('ミュートしているユーザーのノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/mute/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('フォローしているユーザーが行ったミュートしているユーザーのリノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/mute/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('withReplies: true でフォローしているユーザーが行ったミュートしているユーザーの投稿への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await api('/following/update', { userId: bob.id, withReplies: true }, alice);
+			await api('/mute/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), false);
+		});
+
+		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { fileIds: [file.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', { withFiles: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		}, 1000 * 10);
+	});
+
+	describe('Social TL', () => {
+		test.concurrent('ローカルユーザーのノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('ローカルユーザーの visibility: home なノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('フォローしているローカルユーザーの visibility: home なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('リモートユーザーのノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/local-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', {}, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { fileIds: [file.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/hybrid-timeline', { withFiles: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		}, 1000 * 10);
+	});
+
+	describe('User List TL', () => {
+		test.concurrent('リスインしているフォローしていないユーザーのノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('リスインしているフォローしていないユーザーの visibility: home なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('リスインしているフォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('リスインしているフォローしていないユーザーの他人への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('リスインしているフォローしていないユーザーのユーザー自身への返信が含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		});
+
+		test.concurrent('withReplies: true でリスインしているフォローしていないユーザーの他人への返信が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await api('/users/lists/update-membership', { listId: list.id, userId: bob.id, withReplies: true }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('リスインしているフォローしているユーザーの visibility: home なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('リスインしているフォローしているユーザーの visibility: followers なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
+		});
+
+		test.concurrent('リスインしているユーザーのチャンネルノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('[withFiles: true] リスインしているユーザーのファイル付きノートのみ含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { fileIds: [file.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id, withFiles: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		}, 1000 * 10);
+
+		test.concurrent('リスインしているユーザーの自身宛ての visibility: specified なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [alice.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
+		});
+
+		test.concurrent('リスインしているユーザーの自身宛てではない visibility: specified なノートが含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const list = await api('/users/lists/create', { name: 'list' }, alice).then(res => res.body);
+			await api('/users/lists/push', { listId: list.id, userId: bob.id }, alice);
+			await api('/users/lists/push', { listId: list.id, userId: carol.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified', visibleUserIds: [carol.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/notes/user-list-timeline', { listId: list.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+	});
+
+	describe('User TL', () => {
+		test.concurrent('ノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('フォローしていないユーザーの visibility: followers なノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('フォローしているユーザーの visibility: followers なノートが含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/following/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote = await post(bob, { text: 'hi', visibility: 'followers' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === bobNote.id).text, 'hi');
+		});
+
+		test.concurrent('自身の visibility: followers なノートが含まれる', async () => {
+			const [alice] = await Promise.all([signup()]);
+
+			const aliceNote = await post(alice, { text: 'hi', visibility: 'followers' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: alice.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+			assert.strictEqual(res.body.find((note: any) => note.id === aliceNote.id).text, 'hi');
+		});
+
+		test.concurrent('チャンネル投稿が含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('[withReplies: false] 他人への返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false);
+		});
+
+		test.concurrent('[withReplies: true] 他人への返信が含まれる', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		});
+
+		test.concurrent('[withReplies: true] 他人への visibility: specified な返信が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { text: 'hi', replyId: carolNote.id, visibility: 'specified' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), false);
+		});
+
+		test.concurrent('[withFiles: true] ファイル付きノートのみ含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const file = await uploadUrl(bob, 'https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png');
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { fileIds: [file.id] });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withFiles: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), false);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+		}, 1000 * 10);
+
+		test.concurrent('[withChannelNotes: true] チャンネル投稿が含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const channel = await api('/channels/create', { name: 'channel' }, bob).then(x => x.body);
+			const bobNote = await post(bob, { text: 'hi', channelId: channel.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withChannelNotes: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), true);
+		});
+
+		test.concurrent('ミュートしているユーザーに関連する投稿が含まれない', async () => {
+			const [alice, bob, carol] = await Promise.all([signup(), signup(), signup()]);
+
+			await api('/mute/create', { userId: carol.id }, alice);
+			await sleep(1000);
+			const carolNote = await post(carol, { text: 'hi' });
+			const bobNote = await post(bob, { text: 'hi', renoteId: carolNote.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+
+		test.concurrent('ミュートしていても userId に指定したユーザーの投稿が含まれる', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			await api('/mute/create', { userId: bob.id }, alice);
+			await sleep(1000);
+			const bobNote1 = await post(bob, { text: 'hi' });
+			const bobNote2 = await post(bob, { text: 'hi', replyId: bobNote1.id });
+			const bobNote3 = await post(bob, { text: 'hi', renoteId: bobNote1.id });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote1.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote2.id), true);
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote3.id), true);
+		});
+
+		test.concurrent('自身の visibility: specified なノートが含まれる', async () => {
+			const [alice] = await Promise.all([signup()]);
+
+			const aliceNote = await post(alice, { text: 'hi', visibility: 'specified' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: alice.id, withReplies: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === aliceNote.id), true);
+		});
+
+		test.concurrent('visibleUserIds に指定されてない visibility: specified なノートが含まれない', async () => {
+			const [alice, bob] = await Promise.all([signup(), signup()]);
+
+			const bobNote = await post(bob, { text: 'hi', visibility: 'specified' });
+
+			await waitForPushToTl();
+
+			const res = await api('/users/notes', { userId: bob.id, withReplies: true }, alice);
+
+			assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false);
+		});
+	});
+
+	// TODO: リノートミュート済みユーザーのテスト
+	// TODO: ページネーションのテスト
+});
diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts
index 121070787d40f5174fac76476299b9caffbcdfcc..b5f00a632752acf3ddd082756e3fff182eccabe4 100644
--- a/packages/backend/test/e2e/user-notes.ts
+++ b/packages/backend/test/e2e/user-notes.ts
@@ -38,23 +38,10 @@ describe('users/notes', () => {
 		await app.close();
 	});
 
-	test('ファイルタイプ指定 (jpg)', async () => {
+	test('withFiles', async () => {
 		const res = await api('/users/notes', {
 			userId: alice.id,
-			fileType: ['image/jpeg'],
-		}, alice);
-
-		assert.strictEqual(res.status, 200);
-		assert.strictEqual(Array.isArray(res.body), true);
-		assert.strictEqual(res.body.length, 2);
-		assert.strictEqual(res.body.some((note: any) => note.id === jpgNote.id), true);
-		assert.strictEqual(res.body.some((note: any) => note.id === jpgPngNote.id), true);
-	});
-
-	test('ファイルタイプ指定 (jpg or png)', async () => {
-		const res = await api('/users/notes', {
-			userId: alice.id,
-			fileType: ['image/jpeg', 'image/png'],
+			withFiles: true,
 		}, alice);
 
 		assert.strictEqual(res.status, 200);
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index 0f5d5f734466ab291aa91a5079835ec785470818..53db1ac28a006a9dfed96bd7745acc4ce42afbb7 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -133,6 +133,7 @@ describe('ユーザー', () => {
 			isMuted: user.isMuted ?? false,
 			isRenoteMuted: user.isRenoteMuted ?? false,
 			notify: user.notify ?? 'none',
+			withReplies: user.withReplies ?? false,
 		});
 	};
 
diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts
index dbc446d12de01d4a512b6374c7a498245513e3c3..2e9454927c7f60e21874393b64cf3e47594e5f9d 100644
--- a/packages/backend/test/unit/activitypub.ts
+++ b/packages/backend/test/unit/activitypub.ts
@@ -92,6 +92,9 @@ describe('ActivityPub', () => {
 	const metaInitial = {
 		cacheRemoteFiles: true,
 		cacheRemoteSensitiveFiles: true,
+		perUserHomeTimelineCacheMax: 100,
+		perLocalUserUserTimelineCacheMax: 100,
+		perRemoteUserUserTimelineCacheMax: 100,
 		blockedHosts: [] as string[],
 		sensitiveWords: [] as string[],
 	} as MiMeta;
diff --git a/packages/backend/test/unit/misc/loader.ts b/packages/backend/test/unit/misc/loader.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fa37950951181e06c8dc7803cec1508198ec1d4f
--- /dev/null
+++ b/packages/backend/test/unit/misc/loader.ts
@@ -0,0 +1,88 @@
+import { DebounceLoader } from '@/misc/loader.js';
+
+class Mock {
+	loadCountByKey = new Map<number, number>();
+	load = async (key: number): Promise<number> => {
+		const count = this.loadCountByKey.get(key);
+		if (typeof count === 'undefined') {
+			this.loadCountByKey.set(key, 1);
+		} else {
+			this.loadCountByKey.set(key, count + 1);
+		}
+		return key * 2;
+	};
+	reset() {
+		this.loadCountByKey.clear();
+	}
+}
+
+describe(DebounceLoader, () => {
+	describe('single request', () => {
+		it('loads once', async () => {
+			const mock = new Mock();
+			const loader = new DebounceLoader(mock.load);
+			expect(await loader.load(7)).toBe(14);
+			expect(mock.loadCountByKey.size).toBe(1);
+			expect(mock.loadCountByKey.get(7)).toBe(1);
+		});
+	});
+
+	describe('two duplicated requests at same time', () => {
+		it('loads once', async () => {
+			const mock = new Mock();
+			const loader = new DebounceLoader(mock.load);
+			const [v1, v2] = await Promise.all([
+				loader.load(7),
+				loader.load(7),
+			]);
+			expect(v1).toBe(14);
+			expect(v2).toBe(14);
+			expect(mock.loadCountByKey.size).toBe(1);
+			expect(mock.loadCountByKey.get(7)).toBe(1);
+		});
+	});
+
+	describe('two different requests at same time', () => {
+		it('loads twice', async () => {
+			const mock = new Mock();
+			const loader = new DebounceLoader(mock.load);
+			const [v1, v2] = await Promise.all([
+				loader.load(7),
+				loader.load(13),
+			]);
+			expect(v1).toBe(14);
+			expect(v2).toBe(26);
+			expect(mock.loadCountByKey.size).toBe(2);
+			expect(mock.loadCountByKey.get(7)).toBe(1);
+			expect(mock.loadCountByKey.get(13)).toBe(1);
+		});
+	});
+
+	describe('non-continuous same two requests', () => {
+		it('loads twice', async () => {
+			const mock = new Mock();
+			const loader = new DebounceLoader(mock.load);
+			expect(await loader.load(7)).toBe(14);
+			expect(mock.loadCountByKey.size).toBe(1);
+			expect(mock.loadCountByKey.get(7)).toBe(1);
+			mock.reset();
+			expect(await loader.load(7)).toBe(14);
+			expect(mock.loadCountByKey.size).toBe(1);
+			expect(mock.loadCountByKey.get(7)).toBe(1);
+		});
+	});
+
+	describe('non-continuous different two requests', () => {
+		it('loads twice', async () => {
+			const mock = new Mock();
+			const loader = new DebounceLoader(mock.load);
+			expect(await loader.load(7)).toBe(14);
+			expect(mock.loadCountByKey.size).toBe(1);
+			expect(mock.loadCountByKey.get(7)).toBe(1);
+			mock.reset();
+			expect(await loader.load(13)).toBe(26);
+			expect(mock.loadCountByKey.size).toBe(1);
+			expect(mock.loadCountByKey.get(13)).toBe(1);
+		});
+	});
+});
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index adc532bbe7bcdfe373d9e01ef340f5772cab54b9..97118d73c0e1702648b13e66ea878540410c89c4 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -99,9 +99,17 @@ export const relativeFetch = async (path: string, init?: RequestInit | undefined
 	return await fetch(new URL(path, `http://127.0.0.1:${port}/`).toString(), init);
 };
 
+export function randomString(chars = 'abcdefghijklmnopqrstuvwxyz0123456789', length = 16) {
+	let randomString = '';
+	for (let i = 0; i < length; i++) {
+		randomString += chars[Math.floor(Math.random() * chars.length)];
+	}
+	return randomString;
+}
+
 export const signup = async (params?: Partial<misskey.Endpoints['signup']['req']>): Promise<NonNullable<misskey.Endpoints['signup']['res']>> => {
 	const q = Object.assign({
-		username: 'test',
+		username: randomString(),
 		password: 'test',
 	}, params);
 
@@ -293,12 +301,14 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
 };
 
 export const uploadUrl = async (user: UserToken, url: string) => {
-	let file: any;
+	let resolve: unknown;
+	const file = new Promise(ok => resolve = ok);
 	const marker = Math.random().toString();
 
 	const ws = await connectStream(user, 'main', (msg) => {
 		if (msg.type === 'urlUploadFinished' && msg.body.marker === marker) {
-			file = msg.body.file;
+			ws.close();
+			resolve(msg.body.file);
 		}
 	});
 
@@ -308,9 +318,6 @@ export const uploadUrl = async (user: UserToken, url: string) => {
 		force: true,
 	}, user);
 
-	await sleep(7000);
-	ws.close();
-
 	return file;
 };
 
@@ -450,6 +457,7 @@ export async function testPaginationConsistency<Entity extends { id: string, cre
 	};
 
 	for (const limit of [1, 5, 10, 100, undefined]) {
+		/*
 		// 1. sinceId/DateとuntilId/Dateで両端を指定して取得した結果が期待通りになっていること
 		if (ordering === 'desc') {
 			const end = expected.at(-1)!;
@@ -478,6 +486,7 @@ export async function testPaginationConsistency<Entity extends { id: string, cre
 				actual.map(({ id, createdAt }) => id + ':' + createdAt),
 				expected.map(({ id, createdAt }) => id + ':' + createdAt));
 		}
+		*/
 
 		// 3. untilId指定+limitで取得してつなぎ合わせた結果が期待通りになっていること
 		if (ordering === 'desc') {
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 1f704cd2585af9c00290a4d8527ec627f824e866..8b7604f311a2440966a9791e0027a35857a9c4cc 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -18,13 +18,13 @@
 	"dependencies": {
 		"@discordapp/twemoji": "14.1.2",
 		"@github/webauthn-json": "2.1.1",
-		"@rollup/plugin-alias": "5.0.0",
-		"@rollup/plugin-json": "6.0.0",
-		"@rollup/plugin-replace": "5.0.2",
-		"@rollup/pluginutils": "5.0.4",
+		"@rollup/plugin-alias": "5.0.1",
+		"@rollup/plugin-json": "6.0.1",
+		"@rollup/plugin-replace": "5.0.3",
+		"@rollup/pluginutils": "5.0.5",
 		"@syuilo/aiscript": "0.16.0",
 		"@tabler/icons-webfont": "2.37.0",
-		"@vitejs/plugin-vue": "4.3.4",
+		"@vitejs/plugin-vue": "4.4.0",
 		"@vue-macros/reactivity-transform": "0.3.23",
 		"@vue/compiler-sfc": "3.3.4",
 		"astring": "1.8.6",
@@ -38,7 +38,7 @@
 		"chartjs-chart-matrix": "2.0.1",
 		"chartjs-plugin-gradient": "0.6.1",
 		"chartjs-plugin-zoom": "2.0.1",
-		"chromatic": "7.2.0",
+		"chromatic": "7.2.3",
 		"compare-versions": "6.1.0",
 		"cropperjs": "2.0.0-beta.4",
 		"date-fns": "2.30.0",
@@ -53,13 +53,13 @@
 		"matter-js": "0.19.0",
 		"mfm-js": "0.23.3",
 		"misskey-js": "workspace:*",
-		"photoswipe": "5.4.1",
+		"photoswipe": "5.4.2",
 		"prismjs": "1.29.0",
 		"punycode": "2.3.0",
 		"querystring": "0.2.1",
-		"rollup": "3.29.4",
+		"rollup": "4.0.2",
 		"sanitize-html": "2.11.0",
-		"sass": "1.68.0",
+		"sass": "1.69.1",
 		"strict-event-emitter-types": "2.0.0",
 		"textarea-caret": "3.1.0",
 		"three": "0.157.0",
@@ -72,70 +72,70 @@
 		"uuid": "9.0.1",
 		"v-code-diff": "1.7.1",
 		"vanilla-tilt": "1.8.1",
-		"vite": "4.4.9",
+		"vite": "4.4.11",
 		"vue": "3.3.4",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
-		"@storybook/addon-actions": "7.4.5",
-		"@storybook/addon-essentials": "7.4.5",
-		"@storybook/addon-interactions": "7.4.5",
-		"@storybook/addon-links": "7.4.5",
-		"@storybook/addon-storysource": "7.4.5",
-		"@storybook/addons": "7.4.5",
-		"@storybook/blocks": "7.4.5",
-		"@storybook/core-events": "7.4.5",
-		"@storybook/jest": "0.2.2",
-		"@storybook/manager-api": "7.4.5",
-		"@storybook/preview-api": "7.4.5",
-		"@storybook/react": "7.4.5",
-		"@storybook/react-vite": "7.4.5",
-		"@storybook/testing-library": "0.2.1",
-		"@storybook/theming": "7.4.5",
-		"@storybook/types": "7.4.5",
-		"@storybook/vue3": "7.4.5",
-		"@storybook/vue3-vite": "7.4.5",
+		"@storybook/addon-actions": "7.4.6",
+		"@storybook/addon-essentials": "7.4.6",
+		"@storybook/addon-interactions": "7.4.6",
+		"@storybook/addon-links": "7.4.6",
+		"@storybook/addon-storysource": "7.4.6",
+		"@storybook/addons": "7.4.6",
+		"@storybook/blocks": "7.4.6",
+		"@storybook/core-events": "7.4.6",
+		"@storybook/jest": "0.2.3",
+		"@storybook/manager-api": "7.4.6",
+		"@storybook/preview-api": "7.4.6",
+		"@storybook/react": "7.4.6",
+		"@storybook/react-vite": "7.4.6",
+		"@storybook/testing-library": "0.2.2",
+		"@storybook/theming": "7.4.6",
+		"@storybook/types": "7.4.6",
+		"@storybook/vue3": "7.4.6",
+		"@storybook/vue3-vite": "7.4.6",
 		"@testing-library/vue": "7.0.0",
 		"@types/escape-regexp": "0.0.1",
 		"@types/estree": "1.0.2",
 		"@types/matter-js": "0.19.1",
 		"@types/micromatch": "4.0.3",
-		"@types/node": "20.7.1",
+		"@types/node": "20.8.4",
 		"@types/punycode": "2.1.0",
 		"@types/sanitize-html": "2.9.1",
 		"@types/throttle-debounce": "5.0.0",
 		"@types/tinycolor2": "1.4.4",
-		"@types/uuid": "9.0.4",
+		"@types/uuid": "9.0.5",
 		"@types/websocket": "1.0.7",
 		"@types/ws": "8.5.6",
-		"@typescript-eslint/eslint-plugin": "6.7.3",
-		"@typescript-eslint/parser": "6.7.3",
-		"@vitest/coverage-v8": "0.34.5",
+		"@typescript-eslint/eslint-plugin": "6.7.5",
+		"@typescript-eslint/parser": "6.7.5",
+		"@vitest/coverage-v8": "0.34.6",
 		"@vue/runtime-core": "3.3.4",
 		"acorn": "8.10.0",
 		"cross-env": "7.0.3",
 		"cypress": "13.3.0",
-		"eslint": "8.50.0",
+		"eslint": "8.51.0",
 		"eslint-plugin-import": "2.28.1",
 		"eslint-plugin-vue": "9.17.0",
 		"fast-glob": "3.3.1",
 		"happy-dom": "10.0.3",
 		"micromatch": "4.0.5",
-		"msw": "1.3.1",
+		"msw": "1.3.2",
 		"msw-storybook-addon": "1.8.0",
 		"nodemon": "3.0.1",
 		"prettier": "3.0.3",
 		"react": "18.2.0",
 		"react-dom": "18.2.0",
 		"start-server-and-test": "2.0.1",
-		"storybook": "7.4.5",
+		"storybook": "7.4.6",
 		"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
 		"summaly": "github:misskey-dev/summaly",
 		"vite-plugin-turbosnap": "1.0.3",
-		"vitest": "0.34.5",
+		"vitest": "0.34.6",
 		"vitest-fetch-mock": "0.2.2",
-		"vue-eslint-parser": "9.3.1",
-		"vue-tsc": "1.8.15"
+		"vue-eslint-parser": "9.3.2",
+		"vue-tsc": "1.8.18"
 	}
 }
diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue
index 54c215935628f8cf69eaff074812d5798e5502a8..0cdaf7c9bda51516ac4deb8808aabf4d4161bd72 100644
--- a/packages/frontend/src/components/MkCwButton.vue
+++ b/packages/frontend/src/components/MkCwButton.vue
@@ -4,10 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<button class="_button" :class="$style.root" @mousedown="toggle">
-	<b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b>
-	<span v-if="!modelValue" :class="$style.label">{{ label }}</span>
-</button>
+<MkButton rounded full small @click="toggle"><b>{{ modelValue ? i18n.ts._cw.hide : i18n.ts._cw.show }}</b><span v-if="!modelValue" :class="$style.label">{{ label }}</span></MkButton>
 </template>
 
 <script lang="ts" setup>
@@ -15,6 +12,7 @@ import { computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import { concat } from '@/scripts/array.js';
 import { i18n } from '@/i18n.js';
+import MkButton from '@/components/MkButton.vue';
 
 const props = defineProps<{
 	modelValue: boolean;
@@ -33,25 +31,12 @@ const label = computed(() => {
 	] as string[][]).join(' / ');
 });
 
-const toggle = () => {
+function toggle() {
 	emit('update:modelValue', !props.modelValue);
-};
+}
 </script>
 
 <style lang="scss" module>
-.root {
-	display: inline-block;
-	padding: 4px 8px;
-	font-size: 0.7em;
-	color: var(--cwFg);
-	background: var(--cwBg);
-	border-radius: 2px;
-
-	&:hover {
-		background: var(--cwHoverBg);
-	}
-}
-
 .label {
 	margin-left: 4px;
 
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index e3f96724d9dcd024b01f333d2aa79ea9c450df4f..96704996f9c3c602b7b788b81ecf29c9482bf45a 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -45,8 +45,11 @@ import bytes from '@/filters/bytes.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
+import { useRouter } from '@/router.js';
 import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
 
+const router = useRouter();
+
 const props = withDefaults(defineProps<{
 	file: Misskey.entities.DriveFile;
 	folder: Misskey.entities.DriveFolder | null;
@@ -71,7 +74,7 @@ function onClick(ev: MouseEvent) {
 	if (props.selectMode) {
 		emit('chosen', props.file);
 	} else {
-		os.popupMenu(getDriveFileMenu(props.file, props.folder), (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined);
+		router.push(`/my/drive/file/${props.file.id}`);
 	}
 }
 
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 315ce958c5a7f0c14b82fb23aef88cbe0feac2c8..5cfd7eb534aa97408aeaf166862840ebf89cfac3 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -23,6 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:spellcheck="spellcheck"
 			:step="step"
 			:list="id"
+			:min="min"
+			:max="max"
 			@focus="focused = true"
 			@blur="focused = false"
 			@keydown="onKeydown($event)"
@@ -59,6 +61,8 @@ const props = defineProps<{
 	spellcheck?: boolean;
 	step?: any;
 	datalist?: string[];
+	min?: number;
+	max?: number;
 	inline?: boolean;
 	debounce?: boolean;
 	manualSave?: boolean;
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index 10b2ac9ece16d27267e74952d5b1e783ff46ba7c..69da1a7466847a28bb4a718b6b776614da7cd70c 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -17,7 +17,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:title="media.name"
 			controls
 			preload="metadata"
-			@volumechange="volumechange"
 		/>
 	</div>
 	<a
@@ -33,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { soundConfigStore } from '@/scripts/sound.js';
 import { i18n } from '@/i18n.js';
@@ -43,15 +42,13 @@ const props = withDefaults(defineProps<{
 }>(), {
 });
 
-const audioEl = $shallowRef<HTMLAudioElement | null>();
+const audioEl = shallowRef<HTMLAudioElement>();
 let hide = $ref(true);
 
-function volumechange() {
-	if (audioEl) soundConfigStore.set('mediaVolume', audioEl.volume);
-}
-
-onMounted(() => {
-	if (audioEl) audioEl.volume = soundConfigStore.state.mediaVolume;
+watch(audioEl, () => {
+	if (audioEl.value) {
+		audioEl.value.volume = 0.3;
+	}
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 751b5f75700a712bdba9bde3e661b57e88456fd2..43c64b4c85cb33e51f221e5256d83ca9f7e33995 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </div>
 <div v-else :class="[$style.visible, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]">
 	<video
+		ref="videoEl"
 		:class="$style.video"
 		:poster="video.thumbnailUrl"
 		:title="video.comment"
@@ -31,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import bytes from '@/filters/bytes.js';
 import { defaultStore } from '@/store.js';
@@ -42,6 +43,14 @@ const props = defineProps<{
 }>();
 
 const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
+
+const videoEl = shallowRef<HTMLVideoElement>();
+
+watch(videoEl, () => {
+	if (videoEl.value) {
+		videoEl.value.volume = 0.3;
+	}
+});
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index b397f3eee9f02dfb4c0a9a3612fbf7fd09099641..5272bf865e4c24734da599e84024189d51102645 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div style="container-type: inline-size;">
 				<p v-if="appearNote.cw != null" :class="$style.cw">
 					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
-					<MkCwButton v-model="showContent" :note="appearNote"/>
+					<MkCwButton v-model="showContent" :note="appearNote" style="margin: 4px 0;"/>
 				</p>
 				<div v-show="appearNote.cw == null || showContent" :class="[{ [$style.contentCollapsed]: collapsed }]">
 					<div :class="$style.text">
@@ -165,7 +165,7 @@ import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getNoteSummary } from '@/scripts/get-note-summary.js';
-import { MenuItem } from '@/types/menu';
+import { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import { shouldCollapsed } from '@/scripts/collapsed.js';
@@ -211,11 +211,11 @@ const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : n
 const isLong = shouldCollapsed(appearNote);
 const collapsed = ref(appearNote.cw == null && isLong);
 const isDeleted = ref(false);
-const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
+const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
 const translation = ref<any>(null);
 const translating = ref(false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i.id));
 let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
 
 const keymap = {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index ab8886e8ba9f823fe6a7052a0015221de19e48c0..a1360aba9dc6c713a3dfdbe69f20c48d08dab9cb 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -93,9 +93,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 		<footer>
 			<div :class="$style.noteFooterInfo">
-				<div v-if="appearNote.updatedAt">
-					{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
-				</div>
 				<MkA :to="notePage(appearNote)">
 					<MkTime :time="appearNote.createdAt" mode="detail"/>
 				</MkA>
@@ -214,7 +211,7 @@ import { useNoteCapture } from '@/scripts/use-note-capture.js';
 import { deepClone } from '@/scripts/clone.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { claimAchievement } from '@/scripts/achievements.js';
-import { MenuItem } from '@/types/menu';
+import { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -258,7 +255,7 @@ let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
 const isDeleted = ref(false);
-const muted = ref(checkWordMute(appearNote, $i, defaultStore.state.mutedWords));
+const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
 const translation = ref(null);
 const translating = ref(false);
 const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 05f98c638ef236057e1e6121cfeb84789ffe4233..dda7238d273bfe0eade74b8f99e6643f58b874bc 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -14,7 +14,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
 	</div>
 	<div :class="$style.info">
-		<span v-if="note.updatedAt" style="margin-right: 0.5em;" :title="i18n.ts.edited"><i class="ti ti-pencil"></i></span>
 		<MkA :to="notePage(note)">
 			<MkTime :time="note.createdAt"/>
 		</MkA>
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 2a3cd9bf022d16270fb2b98a882cdf86fe2e620f..bc52101f429e81a169d4029553bbf34e1f4df48a 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -49,9 +49,9 @@ import { notePage } from '@/filters/note.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { userPage } from "@/filters/user";
-import { checkWordMute } from "@/scripts/check-word-mute";
-import { defaultStore } from "@/store";
+import { userPage } from '@/filters/user.js';
+import { checkWordMute } from '@/scripts/check-word-mute.js';
+import { defaultStore } from '@/store.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -63,7 +63,7 @@ const props = withDefaults(defineProps<{
 	depth: 1,
 });
 
-const muted = ref(checkWordMute(props.note, $i, defaultStore.state.mutedWords));
+const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
 
 let showContent = $ref(false);
 let replies: Misskey.entities.Note[] = $ref([]);
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index b82ca3ef19f187f9c47fc7aa5d92e5f730efb481..f6981cea72f146157b7d1a7381933ca975f807c7 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -143,7 +143,6 @@ const props = withDefaults(defineProps<{
 	fixed?: boolean;
 	autofocus?: boolean;
 	freezeAfterPosted?: boolean;
-	updateMode?: boolean;
 }>(), {
 	initialVisibleUsers: () => [],
 	autofocus: true,
@@ -710,7 +709,6 @@ async function post(ev?: MouseEvent) {
 		visibility: visibility,
 		visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
 		reactionAcceptance,
-		noteId: props.updateMode ? props.initialNote?.id : undefined,
 	};
 
 	if (withHashtags && hashtags && hashtags.trim() !== '') {
@@ -733,7 +731,7 @@ async function post(ev?: MouseEvent) {
 	}
 
 	posting = true;
-	os.api(props.updateMode ? 'notes/update' : 'notes/create', postData, token).then(() => {
+	os.api('notes/create', postData, token).then(() => {
 		if (props.freezeAfterPosted) {
 			posted = true;
 		} else {
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index f33d498f93a8b6f8e754a163439196501cb87049..c07a166a83184cf23d6fef6fc48d595ac12090c9 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -30,7 +30,6 @@ const props = defineProps<{
 	instant?: boolean;
 	fixed?: boolean;
 	autofocus?: boolean;
-	updateMode?: boolean;
 }>();
 
 const emit = defineEmits<{
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
index aa4a184d7b8851179d9180a41d9fa43a96bbf22f..76163ab68b05f4cf6cd05dca734fe4fc62182622 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.vue
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -30,13 +30,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch :modelValue="agreeServerRules" style="margin-top: 16px;" @update:modelValue="updateAgreeServerRules">{{ i18n.ts.agree }}</MkSwitch>
 			</MkFolder>
 
-			<MkFolder v-if="availableTos" :defaultOpen="true">
-				<template #label>{{ i18n.ts.termsOfService }}</template>
-				<template #suffix><i v-if="agreeTos" class="ti ti-check" style="color: var(--success)"></i></template>
-
-				<a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a>
-
-				<MkSwitch :modelValue="agreeTos" style="margin-top: 16px;" @update:modelValue="updateAgreeTos">{{ i18n.ts.agree }}</MkSwitch>
+			<MkFolder v-if="availableTos || availablePrivacyPolicy" :defaultOpen="true">
+				<template #label>{{ tosPrivacyPolicyLabel }}</template>
+				<template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--success)"></i></template>
+				<div class="_gaps_s">
+					<div v-if="availableTos"><a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a></div>
+					<div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl" class="_link" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ti ti-external-link"></i></a></div>
+				</div>
+
+				<MkSwitch :modelValue="agreeTosAndPrivacyPolicy" style="margin-top: 16px;" @update:modelValue="updateAgreeTosAndPrivacyPolicy">{{ i18n.ts.agree }}</MkSwitch>
 			</MkFolder>
 
 			<MkFolder :defaultOpen="true">
@@ -70,14 +72,15 @@ import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
 
 const availableServerRules = instance.serverRules.length > 0;
-const availableTos = instance.tosUrl != null;
+const availableTos = instance.tosUrl != null && instance.tosUrl !== '';
+const availablePrivacyPolicy = instance.privacyPolicyUrl != null && instance.privacyPolicyUrl !== '';
 
 const agreeServerRules = ref(false);
-const agreeTos = ref(false);
+const agreeTosAndPrivacyPolicy = ref(false);
 const agreeNote = ref(false);
 
 const agreed = computed(() => {
-	return (!availableServerRules || agreeServerRules.value) && (!availableTos || agreeTos.value) && agreeNote.value;
+	return (!availableServerRules || agreeServerRules.value) && ((!availableTos && !availablePrivacyPolicy) || agreeTosAndPrivacyPolicy.value) && agreeNote.value;
 });
 
 const emit = defineEmits<{
@@ -85,6 +88,18 @@ const emit = defineEmits<{
 	(ev: 'done'): void;
 }>();
 
+const tosPrivacyPolicyLabel = computed(() => {
+	if (availableTos && availablePrivacyPolicy) {
+		return i18n.ts.tosAndPrivacyPolicy;
+	} else if (availableTos) {
+		return i18n.ts.termsOfService;
+	} else if (availablePrivacyPolicy) {
+		return i18n.ts.privacyPolicy;
+	} else {
+		return "";
+	}
+});
+
 async function updateAgreeServerRules(v: boolean) {
 	if (v) {
 		const confirm = await os.confirm({
@@ -99,17 +114,19 @@ async function updateAgreeServerRules(v: boolean) {
 	}
 }
 
-async function updateAgreeTos(v: boolean) {
+async function updateAgreeTosAndPrivacyPolicy(v: boolean) {
 	if (v) {
 		const confirm = await os.confirm({
 			type: 'question',
 			title: i18n.ts.doYouAgree,
-			text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.termsOfService }),
+			text: i18n.t('iHaveReadXCarefullyAndAgree', {
+				x: tosPrivacyPolicyLabel.value,
+			}),
 		});
 		if (confirm.canceled) return;
-		agreeTos.value = true;
+		agreeTosAndPrivacyPolicy.value = true;
 	} else {
-		agreeTos.value = false;
+		agreeTosAndPrivacyPolicy.value = false;
 	}
 }
 
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 1dcafd6be11790ddb400188add8069ff41814595..45dedd5042c1b0a9e9efb1ce4b25ed8ec6e94af3 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -13,6 +13,7 @@ import MkNotes from '@/components/MkNotes.vue';
 import { useStream } from '@/stream.js';
 import * as sound from '@/scripts/sound.js';
 import { $i } from '@/account.js';
+import { instance } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 
 const props = withDefaults(defineProps<{
@@ -23,11 +24,9 @@ const props = withDefaults(defineProps<{
 	role?: string;
 	sound?: boolean;
 	withRenotes?: boolean;
-	withReplies?: boolean;
 	onlyFiles?: boolean;
 }>(), {
 	withRenotes: true,
-	withReplies: false,
 	onlyFiles: false,
 });
 
@@ -40,7 +39,15 @@ provide('inChannel', computed(() => props.src === 'channel'));
 
 const tlComponent: InstanceType<typeof MkNotes> = $ref();
 
+let tlNotesCount = 0;
+
 const prepend = note => {
+	tlNotesCount++;
+
+	if (instance.notesPerOneAd > 0 && tlNotesCount % instance.notesPerOneAd === 0) {
+		note._shouldInsertAd_ = true;
+	}
+
 	tlComponent.pagingComponent?.prepend(note);
 
 	emit('note');
@@ -70,12 +77,10 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/timeline';
 	query = {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('homeTimeline', {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -85,12 +90,10 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/local-timeline';
 	query = {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('localTimeline', {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -98,12 +101,10 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/hybrid-timeline';
 	query = {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('hybridTimeline', {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -111,12 +112,10 @@ if (props.src === 'antenna') {
 	endpoint = 'notes/global-timeline';
 	query = {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	};
 	connection = stream.useChannel('globalTimeline', {
 		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 	});
 	connection.on('note', prepend);
@@ -139,14 +138,10 @@ if (props.src === 'antenna') {
 } else if (props.src === 'list') {
 	endpoint = 'notes/user-list-timeline';
 	query = {
-		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 		listId: props.list,
 	};
 	connection = stream.useChannel('userList', {
-		withRenotes: props.withRenotes,
-		withReplies: props.withReplies,
 		withFiles: props.onlyFiles ? true : undefined,
 		listId: props.list,
 	});
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index e4520bbb2d4a0311f95b3287b92546720416e728..40493a5d067a8ebf969f8d72de8d1c0fc2e4a0bc 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -104,7 +104,25 @@ function showMenu(ev) {
 		action: () => {
 			os.pageWindow('/about-misskey');
 		},
-	}, null, {
+	}, null, (instance.impressumUrl) ? {
+		text: i18n.ts.impressum,
+		icon: 'ti ti-file-invoice',
+		action: () => {
+			window.open(instance.impressumUrl, '_blank');
+		},
+	} : undefined, (instance.tosUrl) ? {
+		text: i18n.ts.termsOfService,
+		icon: 'ti ti-notebook',
+		action: () => {
+			window.open(instance.tosUrl, '_blank');
+		},
+	} : undefined, (instance.privacyPolicyUrl) ? {
+		text: i18n.ts.privacyPolicy,
+		icon: 'ti ti-shield-lock',
+		action: () => {
+			window.open(instance.privacyPolicyUrl, '_blank');
+		},
+	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : null, {
 		text: i18n.ts.help,
 		icon: 'ti ti-help-circle',
 		action: () => {
diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts
index f4ce0a096deaed08cfba0daf84507734fc9267e1..3998df9efe576b5cefc4d9b61264b020dc08a9a5 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend/src/const.ts
@@ -61,7 +61,6 @@ export const ROLE_POLICIES = [
 	'gtlAvailable',
 	'ltlAvailable',
 	'canPublicNote',
-	'canEditNote',
 	'canInvite',
 	'inviteLimit',
 	'inviteLimitCycle',
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 37c869f98ee884686b6fdc765bdacfe38225e4bb..f67697db5513a5eb6c11a6443f3a4f91c8d6fb1e 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -187,6 +187,9 @@ const patronsWithIcon = [{
 }, {
 	name: 'フランギ・シュウ',
 	icon: 'https://misskey-hub.net/patrons/3016d37e35f3430b90420176c912d304.jpg',
+}, {
+	name: '百日紅',
+	icon: 'https://misskey-hub.net/patrons/302dce2898dd457ba03c3f7dc037900b.jpg',
 }];
 
 const patrons = [
@@ -287,6 +290,7 @@ const patrons = [
 	'kino3277',
 	'美少女JKぐーちゃん',
 	'てば',
+	'たっくん',
 ];
 
 let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index 02768b07740645ae34cf3a8aef370ee02ea951b6..ee4043f9a500bd116acc3cf69b1706515c83e66b 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -46,14 +46,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<template #value>{{ instance.maintainerEmail }}</template>
 						</MkKeyValue>
 					</FormSplit>
-					<MkFolder v-if="instance.serverRules.length > 0">
-						<template #label>{{ i18n.ts.serverRules }}</template>
-
-						<ol class="_gaps_s" :class="$style.rules">
-							<li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
-						</ol>
-					</MkFolder>
-					<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink>
+					<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>{{ i18n.ts.impressum }}</FormLink>
+					<div class="_formLinks">
+						<MkFolder v-if="instance.serverRules.length > 0">
+							<template #label>{{ i18n.ts.serverRules }}</template>
+
+							<ol class="_gaps_s" :class="$style.rules">
+								<li v-for="item, index in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
+							</ol>
+						</MkFolder>
+						<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink>
+						<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>{{ i18n.ts.privacyPolicy }}</FormLink>
+					</div>
 				</div>
 			</FormSection>
 
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e88860166c0525bae75dd5ccb4b68b99e0681170
--- /dev/null
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -0,0 +1,81 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
+		<FormSuspense :p="init">
+			<FormSection>
+				<template #label>DeepL Translation</template>
+
+				<div class="_gaps_m">
+					<MkInput v-model="deeplAuthKey">
+						<template #prefix><i class="ti ti-key"></i></template>
+						<template #label>DeepL Auth Key</template>
+					</MkInput>
+					<MkSwitch v-model="deeplIsPro">
+						<template #label>Pro account</template>
+					</MkSwitch>
+				</div>
+			</FormSection>
+		</FormSuspense>
+	</MkSpacer>
+	<template #footer>
+		<div :class="$style.footer">
+			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
+				<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+			</MkSpacer>
+		</div>
+	</template>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import XHeader from './_header_.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkButton from '@/components/MkButton.vue';
+import FormSuspense from '@/components/form/suspense.vue';
+import FormSection from '@/components/form/section.vue';
+import * as os from '@/os.js';
+import { fetchInstance } from '@/instance.js';
+import { i18n } from '@/i18n.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+
+let deeplAuthKey: string = $ref('');
+let deeplIsPro: boolean = $ref(false);
+
+async function init() {
+	const meta = await os.api('admin/meta');
+	deeplAuthKey = meta.deeplAuthKey;
+	deeplIsPro = meta.deeplIsPro;
+}
+
+function save() {
+	os.apiWithDialog('admin/update-meta', {
+		deeplAuthKey,
+		deeplIsPro,
+	}).then(() => {
+		fetchInstance();
+	});
+}
+
+const headerActions = $computed(() => []);
+
+const headerTabs = $computed(() => []);
+
+definePageMetadata({
+	title: i18n.ts.externalServices,
+	icon: 'ti ti-link',
+});
+</script>
+
+<style lang="scss" module>
+.footer {
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 944ba7b950e1f4840d1d08debc68af7518d302e6..a508c20cf317c0a2d82e16756d2ff85852c8695b 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -198,6 +198,11 @@ const menuDef = $computed(() => [{
 		text: i18n.ts.proxyAccount,
 		to: '/admin/proxy-account',
 		active: currentPage?.route.name === 'proxy-account',
+	}, {
+		icon: 'ti ti-link',
+		text: i18n.ts.externalServices,
+		to: '/admin/external-services',
+		active: currentPage?.route.name === 'external-services',
 	}, {
 		icon: 'ti ti-adjustments',
 		text: i18n.ts.other,
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 46f92729e8168f67bd6ad970a483d3e2826fb77e..8b160635f71f03b012326f28643b0a00055d44d3 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -25,6 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #label>{{ i18n.ts.tosUrl }}</template>
 					</MkInput>
 
+					<MkInput v-model="privacyPolicyUrl">
+						<template #prefix><i class="ti ti-link"></i></template>
+						<template #label>{{ i18n.ts.privacyPolicyUrl }}</template>
+					</MkInput>
+
 					<MkTextarea v-model="preservedUsernames">
 						<template #label>{{ i18n.ts.preservedUsernames }}</template>
 						<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
@@ -69,6 +74,7 @@ let emailRequiredForSignup: boolean = $ref(false);
 let sensitiveWords: string = $ref('');
 let preservedUsernames: string = $ref('');
 let tosUrl: string | null = $ref(null);
+let privacyPolicyUrl: string | null = $ref(null);
 
 async function init() {
 	const meta = await os.api('admin/meta');
@@ -77,6 +83,7 @@ async function init() {
 	sensitiveWords = meta.sensitiveWords.join('\n');
 	preservedUsernames = meta.preservedUsernames.join('\n');
 	tosUrl = meta.tosUrl;
+	privacyPolicyUrl = meta.privacyPolicyUrl;
 }
 
 function save() {
@@ -84,6 +91,7 @@ function save() {
 		disableRegistration: !enableRegistration,
 		emailRequiredForSignup,
 		tosUrl,
+		privacyPolicyUrl,
 		sensitiveWords: sensitiveWords.split('\n'),
 		preservedUsernames: preservedUsernames.split('\n'),
 	}).then(() => {
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 66561c969e5799b2a9b7c9ef42a8a43cf53db162..0af226f02e3ffb8b9d8f3a5c29ff0f144410efc6 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -29,8 +29,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<span v-else-if="log.type === 'unmarkSensitiveDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
 		<span v-else-if="log.type === 'suspendRemoteInstance'">: {{ log.info.host }}</span>
 		<span v-else-if="log.type === 'unsuspendRemoteInstance'">: {{ log.info.host }}</span>
+		<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
+		<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
+		<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
 		<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
+		<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
 		<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
 		<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
 	</template>
@@ -88,6 +92,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
 			</div>
 		</template>
+		<template v-else-if="log.type === 'updateGlobalAnnouncement'">
+			<div :class="$style.diff">
+				<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
+			</div>
+		</template>
+		<template v-else-if="log.type === 'updateUserAnnouncement'">
+			<div :class="$style.diff">
+				<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
+			</div>
+		</template>
 
 		<details>
 			<summary>raw</summary>
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 8015bb7a7f7393017cbad936ce4d7a0bb54534ad..ead2250af2c384e2fa387f14d2b82936e58e0936 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -160,26 +160,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</MkFolder>
 
-			<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
-				<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
-				<template #suffix>
-					<span v-if="role.policies.canEditNote.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
-					<span v-else>{{ role.policies.canEditNote.value ? i18n.ts.yes : i18n.ts.no }}</span>
-					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.canEditNote)"></i></span>
-				</template>
-				<div class="_gaps">
-					<MkSwitch v-model="role.policies.canEditNote.useDefault" :readonly="readonly">
-						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
-					</MkSwitch>
-					<MkSwitch v-model="role.policies.canEditNote.value" :disabled="role.policies.canEditNote.useDefault" :readonly="readonly">
-						<template #label>{{ i18n.ts.enable }}</template>
-					</MkSwitch>
-					<MkRange v-model="role.policies.canEditNote.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
-						<template #label>{{ i18n.ts._role.priority }}</template>
-					</MkRange>
-				</div>
-			</MkFolder>
-
 			<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
 				<template #label>{{ i18n.ts._role._options.canInvite }}</template>
 				<template #suffix>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 001cf3490e9f4968551dafb66bf2b18074773a22..74de9f73966f627791e54a23cb864931161e6df1 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -48,14 +48,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 							</MkSwitch>
 						</MkFolder>
 
-						<MkFolder v-if="matchQuery([i18n.ts._role._options.canEditNote, 'canEditNote'])">
-							<template #label>{{ i18n.ts._role._options.canEditNote }}</template>
-							<template #suffix>{{ policies.canEditNote ? i18n.ts.yes : i18n.ts.no }}</template>
-							<MkSwitch v-model="policies.canEditNote">
-								<template #label>{{ i18n.ts.enable }}</template>
-							</MkSwitch>
-						</MkFolder>
-
 						<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
 							<template #label>{{ i18n.ts._role._options.canInvite }}</template>
 							<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index f93678d7280215fe43860287abfe1c39681d0929..0072d666c902b3659635067fe43cec58a2c20e81 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -34,6 +34,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</MkInput>
 					</FormSplit>
 
+					<MkInput v-model="impressumUrl">
+						<template #label>{{ i18n.ts.impressumUrl }}</template>
+						<template #prefix><i class="ti ti-link"></i></template>
+						<template #caption>{{ i18n.ts.impressumDescription }}</template>
+					</MkInput>
+
 					<MkTextarea v-model="pinnedUsers">
 						<template #label>{{ i18n.ts.pinnedUsers }}</template>
 						<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
@@ -81,16 +87,40 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</FormSection>
 
 					<FormSection>
-						<template #label>DeepL Translation</template>
+						<template #label>Timeline caching</template>
 
 						<div class="_gaps_m">
-							<MkInput v-model="deeplAuthKey">
-								<template #prefix><i class="ti ti-key"></i></template>
-								<template #label>DeepL Auth Key</template>
+							<MkInput v-model="perLocalUserUserTimelineCacheMax" type="number">
+								<template #label>perLocalUserUserTimelineCacheMax</template>
+							</MkInput>
+
+							<MkInput v-model="perRemoteUserUserTimelineCacheMax" type="number">
+								<template #label>perRemoteUserUserTimelineCacheMax</template>
+							</MkInput>
+
+							<MkInput v-model="perUserHomeTimelineCacheMax" type="number">
+								<template #label>perUserHomeTimelineCacheMax</template>
+							</MkInput>
+
+							<MkInput v-model="perUserListTimelineCacheMax" type="number">
+								<template #label>perUserListTimelineCacheMax</template>
 							</MkInput>
-							<MkSwitch v-model="deeplIsPro">
-								<template #label>Pro account</template>
-							</MkSwitch>
+						</div>
+					</FormSection>
+
+					<FormSection>
+						<template #label>{{ i18n.ts._ad.adsSettings }}</template>
+
+						<div class="_gaps_m">
+							<div class="_gaps_s">
+								<MkInput v-model="notesPerOneAd" :min="0" type="number">
+									<template #label>{{ i18n.ts._ad.notesPerOneAd }}</template>
+									<template #caption>{{ i18n.ts._ad.setZeroToDisable }}</template>
+								</MkInput>
+								<MkInfo v-if="notesPerOneAd > 0 && notesPerOneAd < 20" :warn="true">
+									{{ i18n.ts._ad.adsTooClose }}
+								</MkInfo>
+							</div>
 						</div>
 					</FormSection>
 				</div>
@@ -113,6 +143,7 @@ import XHeader from './_header_.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSplit from '@/components/form/split.vue';
 import FormSuspense from '@/components/form/suspense.vue';
@@ -127,14 +158,18 @@ let shortName: string | null = $ref(null);
 let description: string | null = $ref(null);
 let maintainerName: string | null = $ref(null);
 let maintainerEmail: string | null = $ref(null);
+let impressumUrl: string | null = $ref(null);
 let pinnedUsers: string = $ref('');
 let cacheRemoteFiles: boolean = $ref(false);
 let cacheRemoteSensitiveFiles: boolean = $ref(false);
 let enableServiceWorker: boolean = $ref(false);
 let swPublicKey: any = $ref(null);
 let swPrivateKey: any = $ref(null);
-let deeplAuthKey: string = $ref('');
-let deeplIsPro: boolean = $ref(false);
+let perLocalUserUserTimelineCacheMax: number = $ref(0);
+let perRemoteUserUserTimelineCacheMax: number = $ref(0);
+let perUserHomeTimelineCacheMax: number = $ref(0);
+let perUserListTimelineCacheMax: number = $ref(0);
+let notesPerOneAd: number = $ref(0);
 
 async function init(): Promise<void> {
 	const meta = await os.api('admin/meta');
@@ -143,34 +178,42 @@ async function init(): Promise<void> {
 	description = meta.description;
 	maintainerName = meta.maintainerName;
 	maintainerEmail = meta.maintainerEmail;
+	impressumUrl = meta.impressumUrl;
 	pinnedUsers = meta.pinnedUsers.join('\n');
 	cacheRemoteFiles = meta.cacheRemoteFiles;
 	cacheRemoteSensitiveFiles = meta.cacheRemoteSensitiveFiles;
 	enableServiceWorker = meta.enableServiceWorker;
 	swPublicKey = meta.swPublickey;
 	swPrivateKey = meta.swPrivateKey;
-	deeplAuthKey = meta.deeplAuthKey;
-	deeplIsPro = meta.deeplIsPro;
+	perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax;
+	perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax;
+	perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax;
+	perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax;
+	notesPerOneAd = meta.notesPerOneAd;
 }
 
-function save(): void {
-	os.apiWithDialog('admin/update-meta', {
+async function save(): void {
+	await os.apiWithDialog('admin/update-meta', {
 		name,
 		shortName: shortName === '' ? null : shortName,
 		description,
 		maintainerName,
 		maintainerEmail,
+		impressumUrl,
 		pinnedUsers: pinnedUsers.split('\n'),
 		cacheRemoteFiles,
 		cacheRemoteSensitiveFiles,
 		enableServiceWorker,
 		swPublicKey,
 		swPrivateKey,
-		deeplAuthKey,
-		deeplIsPro,
-	}).then(() => {
-		fetchInstance();
+		perLocalUserUserTimelineCacheMax,
+		perRemoteUserUserTimelineCacheMax,
+		perUserHomeTimelineCacheMax,
+		perUserListTimelineCacheMax,
+		notesPerOneAd,
 	});
+
+	fetchInstance();
 }
 
 const headerTabs = $computed(() => []);
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 00e88cbbfb71334d3947f8013221f9b3c8a1a46c..911f4e95d2e28d0542af7868484c49fad32e79c7 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -102,7 +102,6 @@ let searchKey = $ref('');
 const featuredPagination = $computed(() => ({
 	endpoint: 'notes/featured' as const,
 	limit: 10,
-	offsetMode: true,
 	params: {
 		channelId: props.channelId,
 	},
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ae9256b8e3141dca997da29a2b7082b1e5ea14c2
--- /dev/null
+++ b/packages/frontend/src/pages/drive.file.info.vue
@@ -0,0 +1,302 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+	<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
+	<MkLoading v-if="fetching"/>
+	<div v-else-if="file" class="_gaps">
+		<div :class="$style.filePreviewRoot">
+			<MkMediaList :mediaList="[file]"></MkMediaList>
+		</div>
+		<div :class="$style.fileQuickActionsRoot">
+			<button class="_button" :class="$style.fileNameEditBtn" @click="rename()">
+				<h2 class="_nowrap" :class="$style.fileName">{{ file.name }}</h2>
+				<i class="ti ti-pencil" :class="$style.fileNameEditIcon"></i>
+			</button>
+			<div :class="$style.fileQuickActionsOthers">
+				<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
+					<i class="ti ti-pencil"></i>
+				</button>
+				<button v-if="isImage" v-tooltip="i18n.ts.cropImage" class="_button" :class="$style.fileQuickActionsOthersButton" @click="crop()">
+					<i class="ti ti-crop"></i>
+				</button>
+				<button v-if="file.isSensitive" v-tooltip="i18n.ts.unmarkAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
+					<i class="ti ti-eye"></i>
+				</button>
+				<button v-else v-tooltip="i18n.ts.markAsSensitive" class="_button" :class="$style.fileQuickActionsOthersButton" @click="toggleSensitive()">
+					<i class="ti ti-eye-exclamation"></i>
+				</button>
+				<a v-tooltip="i18n.ts.download" :href="file.url" :download="file.name" class="_button" :class="$style.fileQuickActionsOthersButton">
+					<i class="ti ti-download"></i>
+				</a>
+				<button v-tooltip="i18n.ts.delete" class="_button" :class="[$style.fileQuickActionsOthersButton, $style.danger]" @click="deleteFile()">
+					<i class="ti ti-trash"></i>
+				</button>
+			</div>
+		</div>
+		<div>
+			<button class="_button" :class="$style.fileAltEditBtn" @click="describe()">
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.description }}</template>
+					<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ti ti-pencil" :class="$style.fileAltEditIcon"></i></template>
+				</MkKeyValue>
+			</button>
+			<MkKeyValue :class="$style.fileMetaDataChildren">
+				<template #key>{{ i18n.ts._fileViewer.uploadedAt }}</template>
+				<template #value><MkTime :time="file.createdAt" mode="detail"/></template>
+			</MkKeyValue>
+			<MkKeyValue :class="$style.fileMetaDataChildren">
+				<template #key>{{ i18n.ts._fileViewer.type }}</template>
+				<template #value>{{ file.type }}</template>
+			</MkKeyValue>
+			<MkKeyValue :class="$style.fileMetaDataChildren">
+				<template #key>{{ i18n.ts._fileViewer.size }}</template>
+				<template #value>{{ bytes(file.size) }}</template>
+			</MkKeyValue>
+		</div>
+	</div>
+	<div v-else class="_fullinfo">
+		<img :src="infoImageUrl" class="_ghost"/>
+		<div>{{ i18n.ts.nothing }}</div>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, defineAsyncComponent, onMounted } from 'vue';
+import * as Misskey from 'misskey-js';
+import MkInfo from '@/components/MkInfo.vue';
+import MkMediaList from '@/components/MkMediaList.vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import bytes from '@/filters/bytes.js';
+import { infoImageUrl } from '@/instance.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+import { useRouter } from '@/router.js';
+
+const router = useRouter();
+
+const props = defineProps<{
+	fileId: string;
+}>();
+
+const fetching = ref(true);
+const file = ref<Misskey.entities.DriveFile>();
+const isImage = computed(() => file.value?.type.startsWith('image/'));
+
+async function fetch() {
+	fetching.value = true;
+
+	file.value = await os.api('drive/files/show', {
+		fileId: props.fileId,
+	}).catch((err) => {
+		console.error(err);
+		return undefined;
+	});
+
+	fetching.value = false;
+}
+
+function postThis() {
+	if (!file.value) return;
+
+	os.post({
+		initialFiles: [file.value],
+	});
+}
+
+function crop() {
+	if (!file.value) return;
+
+	os.cropImage(file.value, {
+		aspectRatio: NaN,
+		uploadFolder: file.value.folderId ?? null,
+	});
+}
+
+function toggleSensitive() {
+	if (!file.value) return;
+
+	os.apiWithDialog('drive/files/update', {
+		fileId: file.value.id,
+		isSensitive: !file.value.isSensitive,
+	}).then(async () => {
+		await fetch();
+	}).catch(err => {
+		os.alert({
+			type: 'error',
+			title: i18n.ts.error,
+			text: err.message,
+		});
+	});
+}
+
+function rename() {
+	if (!file.value) return;
+
+	os.inputText({
+		title: i18n.ts.renameFile,
+		placeholder: i18n.ts.inputNewFileName,
+		default: file.value.name,
+	}).then(({ canceled, result: name }) => {
+		if (canceled) return;
+		os.apiWithDialog('drive/files/update', {
+			fileId: file.value.id,
+			name: name,
+		}).then(async () => {
+			await fetch();
+		});
+	});
+}
+
+function describe() {
+	if (!file.value) return;
+
+	os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
+		default: file.value.comment ?? '',
+		file: file.value,
+	}, {
+		done: caption => {
+			os.apiWithDialog('drive/files/update', {
+				fileId: file.value.id,
+				comment: caption.length === 0 ? null : caption,
+			}).then(async () => {
+				await fetch();
+			});
+		},
+	}, 'closed');
+}
+
+async function deleteFile() {
+	if (!file.value) return;
+
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('driveFileDeleteConfirm', { name: file.value.name }),
+	});
+
+	if (canceled) return;
+	await os.apiWithDialog('drive/files/delete', {
+		fileId: file.value.id,
+	});
+
+	router.push('/my/drive');
+}
+
+onMounted(async () => {
+	await fetch();
+});
+</script>
+
+<style lang="scss" module>
+
+.filePreviewRoot {
+	background: var(--panel);
+	border-radius: var(--radius);
+	// MkMediaList 内の上部マージン 4px
+	padding: calc(1rem - 4px) 1rem 1rem;
+}
+
+.fileQuickActionsRoot {
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+}
+
+@container (min-width: 500px) {
+	.fileQuickActionsRoot {
+		flex-direction: row;
+		align-items: center;
+	}
+}
+
+.fileQuickActionsOthers {
+	margin-left: auto;
+	margin-right: 1rem;
+	display: flex;
+	gap: 8px;
+
+	.fileQuickActionsOthersButton {
+		padding: .5rem;
+		border-radius: 99rem;
+
+		&:hover,
+		&:focus-visible {
+			background-color: var(--accentedBg);
+			color: var(--accent);
+			text-decoration: none;
+		}
+
+		&.danger {
+			color: #ff2a2a;
+		}
+
+		&.danger:hover,
+		&.danger:focus-visible {
+			background-color: rgba(255, 42, 42, .15);
+		}
+	}
+}
+
+.fileNameEditBtn {
+	padding: .5rem 1rem;
+	display: flex;
+	align-items: center;
+	min-width: 0;
+	font-weight: 700;
+	border-radius: var(--radius);
+	font-size: .8rem;
+
+	>.fileNameEditIcon {
+		color: transparent;
+		visibility: hidden;
+		padding-left: .5rem;
+	}
+
+	>.fileName {
+		margin: 0;
+	}
+
+	&:hover {
+		background-color: var(--accentedBg);
+
+		>.fileName,
+		>.fileNameEditIcon {
+			visibility: visible;
+			color: var(--accent);
+		}
+	}
+}
+
+.fileMetaDataChildren {
+	padding: .5rem 1rem;
+}
+
+.fileAltEditBtn {
+	text-align: start;
+	display: block;
+	width: 100%;
+	padding: .5rem 1rem;
+	border-radius: var(--radius);
+
+	.fileAltEditIcon {
+		display: inline-block;
+		color: transparent;
+		visibility: hidden;
+		padding-left: .5rem;
+	}
+
+	&:hover {
+		color: var(--accent);
+		background-color: var(--accentedBg);
+
+		.fileAltEditIcon {
+			color: var(--accent);
+			visibility: visible;
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/pages/drive.file.notes.vue b/packages/frontend/src/pages/drive.file.notes.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ee1a0ee9b0e5fc96867c15d97da94ed9396da9d4
--- /dev/null
+++ b/packages/frontend/src/pages/drive.file.notes.vue
@@ -0,0 +1,33 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_gaps">
+	<MkInfo>{{ i18n.ts._fileViewer.thisPageCanBeSeenFromTheAuthor }}</MkInfo>
+	<MkNotes ref="tlComponent" :pagination="pagination"/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { ref, computed } from 'vue';
+import { i18n } from '@/i18n.js';
+import { Paging } from '@/components/MkPagination.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkNotes from '@/components/MkNotes.vue';
+
+const props = defineProps<{
+	fileId: string;
+}>();
+
+const realFileId = computed(() => props.fileId);
+
+const pagination = ref<Paging>({
+	endpoint: 'drive/files/attached-notes',
+	limit: 10,
+	params: {
+		fileId: realFileId.value,
+	},
+});
+</script>
diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2c1e5d20a77e123df7d0bd2be94bc1f979f78d9a
--- /dev/null
+++ b/packages/frontend/src/pages/drive.file.vue
@@ -0,0 +1,52 @@
+<!--
+SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+	<template #header>
+		<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
+	</template>
+
+	<MkSpacer v-if="tab === 'info'" :contentMax="800">
+		<XFileInfo :fileId="fileId"/>
+	</MkSpacer>
+
+	<MkSpacer v-else-if="tab === 'notes'" :contentMax="800">
+		<XNotes :fileId="fileId"/>
+	</MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, defineAsyncComponent } from 'vue';
+import { i18n } from '@/i18n.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+
+const props = defineProps<{
+	fileId: string;
+}>();
+
+const XFileInfo = defineAsyncComponent(() => import('./drive.file.info.vue'));
+const XNotes = defineAsyncComponent(() => import('./drive.file.notes.vue'));
+
+const tab = ref('info');
+
+const headerActions = computed(() => []);
+
+const headerTabs = computed(() => [{
+	key: 'info',
+	title: i18n.ts.info,
+	icon: 'ti ti-info-circle',
+}, {
+	key: 'notes',
+	title: i18n.ts._fileViewer.attachedNotes,
+	icon: 'ti ti-pencil',
+}]);
+
+definePageMetadata(computed(() => ({
+	title: i18n.ts._fileViewer.title,
+	icon: 'ti ti-file',
+})));
+</script>
diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue
index 0558faec1629475c18528f59aa8a44b530a758be..a36d1b3bda101dbabbe72bd00e84d0385aeb0411 100644
--- a/packages/frontend/src/pages/explore.featured.vue
+++ b/packages/frontend/src/pages/explore.featured.vue
@@ -22,7 +22,6 @@ import { i18n } from '@/i18n.js';
 const paginationForNotes = {
 	endpoint: 'notes/featured' as const,
 	limit: 10,
-	offsetMode: true,
 };
 
 const paginationForPolls = {
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index df13998f6be46c7710c8c31e9f5d90b5c42761c1..b600f99fbcd02e4f0a09af20ed1d9643f543f0da 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -29,16 +29,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 				<div class="_gaps_s">
 					<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
-					<div v-for="user in users" :key="user.id" :class="$style.userItem">
-						<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
-							<MkUserCardMini :user="user"/>
-						</MkA>
-						<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
-					</div>
-					<MkButton v-if="!fetching && queueUserIds.length !== 0" v-appear="enableInfiniteScroll ? fetchMoreUsers : null" :class="$style.more" :style="{ cursor: 'pointer' }" primary rounded @click="fetchMoreUsers">
-						{{ i18n.ts.loadMore }}
-					</MkButton>
-					<MkLoading v-if="fetching" class="loading"/>
+
+					<MkPagination ref="paginationEl" :pagination="membershipsPagination">
+						<template #default="{ items }">
+							<div class="_gaps_s">
+								<div v-for="item in items" :key="item.id">
+									<div :class="$style.userItem">
+										<MkA :class="$style.userItemBody" :to="`${userPage(item.user)}`">
+											<MkUserCardMini :user="item.user"/>
+										</MkA>
+										<button class="_button" :class="$style.menu" @click="showMembershipMenu(item, $event)"><i class="ti ti-dots"></i></button>
+										<button class="_button" :class="$style.remove" @click="removeUser(item, $event)"><i class="ti ti-x"></i></button>
+									</div>
+								</div>
+							</div>
+						</template>
+					</MkPagination>
 				</div>
 			</MkFolder>
 		</div>
@@ -59,9 +65,11 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkInput from '@/components/MkInput.vue';
-import { userListsCache } from '@/cache';
+import { userListsCache } from '@/cache.js';
 import { $i } from '@/account.js';
 import { defaultStore } from '@/store.js';
+import MkPagination, { Paging } from '@/components/MkPagination.vue';
+
 const {
 	enableInfiniteScroll,
 } = defaultStore.reactiveState;
@@ -70,40 +78,25 @@ const props = defineProps<{
 	listId: string;
 }>();
 
-const FETCH_USERS_LIMIT = 20;
-
+const paginationEl = ref<InstanceType<typeof MkPagination>>();
 let list = $ref<Misskey.entities.UserList | null>(null);
-let users = $ref<Misskey.entities.UserLite[]>([]);
-let queueUserIds = $ref<string[]>([]);
-let fetching = $ref(true);
 const isPublic = ref(false);
 const name = ref('');
+const membershipsPagination = {
+	endpoint: 'users/lists/get-memberships' as const,
+	limit: 30,
+	params: computed(() => ({
+		listId: props.listId,
+	})),
+};
 
 function fetchList() {
-	fetching = true;
 	os.api('users/lists/show', {
 		listId: props.listId,
 	}).then(_list => {
 		list = _list;
 		name.value = list.name;
 		isPublic.value = list.isPublic;
-		queueUserIds = list.userIds;
-
-		return fetchMoreUsers();
-	});
-}
-
-function fetchMoreUsers() {
-	if (!list) return;
-	if (fetching && users.length !== 0) return; // fetchingがtrueならやめるが、usersが空なら続行
-	fetching = true;
-	os.api('users/show', {
-		userIds: queueUserIds.slice(0, FETCH_USERS_LIMIT),
-	}).then(_users => {
-		users = users.concat(_users);
-		queueUserIds = queueUserIds.slice(FETCH_USERS_LIMIT);
-	}).finally(() => {
-		fetching = false;
 	});
 }
 
@@ -114,12 +107,12 @@ function addUser() {
 			listId: list.id,
 			userId: user.id,
 		}).then(() => {
-			users.push(user);
+			paginationEl.value.reload();
 		});
 	});
 }
 
-async function removeUser(user, ev) {
+async function removeUser(item, ev) {
 	os.popupMenu([{
 		text: i18n.ts.remove,
 		icon: 'ti ti-x',
@@ -128,9 +121,28 @@ async function removeUser(user, ev) {
 			if (!list) return;
 			os.api('users/lists/pull', {
 				listId: list.id,
-				userId: user.id,
+				userId: item.userId,
+			}).then(() => {
+				paginationEl.value.removeItem(item.id);
+			});
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
+async function showMembershipMenu(item, ev) {
+	os.popupMenu([{
+		text: item.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
+		icon: item.withReplies ? 'ti ti-messages-off' : 'ti ti-messages',
+		action: async () => {
+			os.api('users/lists/update-membership', {
+				listId: list.id,
+				userId: item.userId,
+				withReplies: !item.withReplies,
 			}).then(() => {
-				users = users.filter(x => x.id !== user.id);
+				paginationEl.value.updateItem(item.id, (old) => ({
+					...old,
+					withReplies: !item.withReplies,
+				}));
 			});
 		},
 	}], ev.currentTarget ?? ev.target);
@@ -202,6 +214,12 @@ definePageMetadata(computed(() => list ? {
 	align-self: center;
 }
 
+.menu {
+	width: 32px;
+	height: 32px;
+	align-self: center;
+}
+
 .more {
 	margin-left: auto;
 	margin-right: auto;
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index 377267bdbcbeecb181cca0a133c5908c83fd626f..dc749c292eb1b5be572315031d81d3b32da6b055 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -286,8 +286,7 @@ definePageMetadata(computed(() => {
 	let title = i18n.ts._pages.newPage;
 	if (props.initPageId) {
 		title = i18n.ts._pages.editPage;
-	}
-	else if (props.initPageName && props.initUser) {
+	} else if (props.initPageName && props.initUser) {
 		title = i18n.ts._pages.readPage;
 	}
 	return {
diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
index c2d1694c5b2442604bd7214ed437274858b351dd..4641b4910321401738bff734716a4e4ac89b8d94 100644
--- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue
+++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
@@ -83,6 +83,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 											<template #value><code class="_monospace">{{ code }}</code></template>
 										</MkKeyValue>
 									</div>
+
+									<MkButton primary rounded gradate @click="downloadBackupCodes"><i class="ti ti-download"></i> {{ i18n.ts.download }}</MkButton>
 								</div>
 							</MkFolder>
 						</div>
@@ -108,6 +110,7 @@ import * as os from '@/os.js';
 import MkFolder from '@/components/MkFolder.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { confetti } from '@/scripts/confetti.js';
+import { $i } from '@/account.js';
 
 defineProps<{
 	twoFactorData: {
@@ -143,6 +146,16 @@ async function tokenDone() {
 	});
 }
 
+function downloadBackupCodes() {
+	if (backupCodes.value !== undefined) {
+		const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' });
+		const dummya = document.createElement('a');
+		dummya.href = URL.createObjectURL(txtBlob);
+		dummya.download = `${$i?.username}-2fa-backup-codes.txt`;
+		dummya.click();
+	}
+}
+
 function allDone() {
 	dialog.value.close();
 }
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index f1bd501150747dd3e10526b80006ad73556fa703..cfabbbbf65239a0c76d39364fb7a3764c8d9be57 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -139,21 +139,11 @@ const menuDef = computed(() => [{
 		text: i18n.ts.roles,
 		to: '/settings/roles',
 		active: currentPage?.route.name === 'roles',
-	}, {
-		icon: 'ti ti-planet-off',
-		text: i18n.ts.instanceMute,
-		to: '/settings/instance-mute',
-		active: currentPage?.route.name === 'instance-mute',
 	}, {
 		icon: 'ti ti-ban',
 		text: i18n.ts.muteAndBlock,
 		to: '/settings/mute-block',
 		active: currentPage?.route.name === 'mute-block',
-	}, {
-		icon: 'ti ti-message-off',
-		text: i18n.ts.wordMute,
-		to: '/settings/word-mute',
-		active: currentPage?.route.name === 'word-mute',
 	}, {
 		icon: 'ti ti-api',
 		text: 'API',
diff --git a/packages/frontend/src/pages/settings/instance-mute.vue b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
similarity index 85%
rename from packages/frontend/src/pages/settings/instance-mute.vue
rename to packages/frontend/src/pages/settings/mute-block.instance-mute.vue
index b76fd2c90636127bb38cb4e2740a65ddefbed090..4b5080ea8fa057f7b9a0fe57fd9f1e3f9d90e160 100644
--- a/packages/frontend/src/pages/settings/instance-mute.vue
+++ b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
@@ -22,7 +22,6 @@ import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const instanceMutes = ref($i!.mutedInstances.join('\n'));
 const changed = ref(false);
@@ -46,13 +45,4 @@ async function save() {
 watch(instanceMutes, () => {
 	changed.value = true;
 });
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata({
-	title: i18n.ts.instanceMute,
-	icon: 'ti ti-planet-off',
-});
 </script>
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index 37d3d1773fb00c3737d2c21136099ffe71794d6a..c6cbd424ec0f37e1251fd18f54dc759dbb18c08c 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -5,13 +5,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div class="_gaps_m">
-	<MkTab v-model="tab">
-		<option value="renoteMute">{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</option>
-		<option value="mute">{{ i18n.ts.mutedUsers }}</option>
-		<option value="block">{{ i18n.ts.blockedUsers }}</option>
-	</MkTab>
+	<MkFolder>
+		<template #icon><i class="ti ti-message-off"></i></template>
+		<template #label>{{ i18n.ts.wordMute }}</template>
+
+		<XWordMute/>
+	</MkFolder>
+
+	<MkFolder>
+		<template #icon><i class="ti ti-planet-off"></i></template>
+		<template #label>{{ i18n.ts.instanceMute }}</template>
+
+		<XInstanceMute/>
+	</MkFolder>
+
+	<MkFolder>
+		<template #icon><i class="ti ti-repeat-off"></i></template>
+		<template #label>{{ i18n.ts.mutedUsers }} ({{ i18n.ts.renote }})</template>
 
-	<div v-if="tab === 'renoteMute'">
 		<MkPagination :pagination="renoteMutingPagination">
 			<template #empty>
 				<div class="_fullinfo">
@@ -37,9 +48,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</template>
 		</MkPagination>
-	</div>
+	</MkFolder>
+
+	<MkFolder>
+		<template #icon><i class="ti ti-eye-off"></i></template>
+		<template #label>{{ i18n.ts.mutedUsers }}</template>
 
-	<div v-else-if="tab === 'mute'">
 		<MkPagination :pagination="mutingPagination">
 			<template #empty>
 				<div class="_fullinfo">
@@ -67,9 +81,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</template>
 		</MkPagination>
-	</div>
+	</MkFolder>
+
+	<MkFolder>
+		<template #icon><i class="ti ti-ban"></i></template>
+		<template #label>{{ i18n.ts.blockedUsers }}</template>
 
-	<div v-else-if="tab === 'block'">
 		<MkPagination :pagination="blockingPagination">
 			<template #empty>
 				<div class="_fullinfo">
@@ -97,24 +114,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</template>
 		</MkPagination>
-	</div>
+	</MkFolder>
 </div>
 </template>
 
 <script lang="ts" setup>
 import { } from 'vue';
+import XInstanceMute from './mute-block.instance-mute.vue';
+import XWordMute from './mute-block.word-mute.vue';
 import MkPagination from '@/components/MkPagination.vue';
-import MkTab from '@/components/MkTab.vue';
-import FormInfo from '@/components/MkInfo.vue';
-import FormLink from '@/components/form/link.vue';
 import { userPage } from '@/filters/user.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import * as os from '@/os.js';
 import { infoImageUrl } from '@/instance.js';
-
-let tab = $ref('renoteMute');
+import MkFolder from '@/components/MkFolder.vue';
 
 const renoteMutingPagination = {
 	endpoint: 'renote-mute/list' as const,
diff --git a/packages/frontend/src/pages/settings/word-mute.vue b/packages/frontend/src/pages/settings/mute-block.word-mute.vue
similarity index 51%
rename from packages/frontend/src/pages/settings/word-mute.vue
rename to packages/frontend/src/pages/settings/mute-block.word-mute.vue
index 1fefbdc92ba131cc936bec974fda61e988f0ca68..25a836ea55e445579463bd39b700dfe2bb96e1d7 100644
--- a/packages/frontend/src/pages/settings/word-mute.vue
+++ b/packages/frontend/src/pages/settings/mute-block.word-mute.vue
@@ -5,29 +5,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div class="_gaps_m">
-	<MkTab v-model="tab">
-		<option value="soft">{{ i18n.ts._wordMute.soft }}</option>
-		<option value="hard">{{ i18n.ts._wordMute.hard }}</option>
-	</MkTab>
 	<div>
-		<div v-show="tab === 'soft'" class="_gaps_m">
-			<MkInfo>{{ i18n.ts._wordMute.softDescription }}</MkInfo>
-			<MkTextarea v-model="softMutedWords">
-				<span>{{ i18n.ts._wordMute.muteWords }}</span>
-				<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
-			</MkTextarea>
-		</div>
-		<div v-show="tab === 'hard'" class="_gaps_m">
-			<MkInfo>{{ i18n.ts._wordMute.hardDescription }} {{ i18n.ts.reflectMayTakeTime }}</MkInfo>
-			<MkTextarea v-model="hardMutedWords">
-				<span>{{ i18n.ts._wordMute.muteWords }}</span>
-				<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
-			</MkTextarea>
-			<MkKeyValue v-if="hardWordMutedNotesCount != null">
-				<template #key>{{ i18n.ts._wordMute.mutedNotes }}</template>
-				<template #value>{{ number(hardWordMutedNotesCount) }}</template>
-			</MkKeyValue>
-		</div>
+		<MkTextarea v-model="mutedWords">
+			<span>{{ i18n.ts._wordMute.muteWords }}</span>
+			<template #caption>{{ i18n.ts._wordMute.muteWordsDescription }}<br>{{ i18n.ts._wordMute.muteWordsDescription2 }}</template>
+		</MkTextarea>
 	</div>
 	<MkButton primary inline :disabled="!changed" @click="save()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 </div>
@@ -56,25 +38,15 @@ const render = (mutedWords) => mutedWords.map(x => {
 }).join('\n');
 
 const tab = ref('soft');
-const softMutedWords = ref(render(defaultStore.state.mutedWords));
-const hardMutedWords = ref(render($i!.mutedWords));
-const hardWordMutedNotesCount = ref(null);
+const mutedWords = ref(render($i!.mutedWords));
 const changed = ref(false);
 
-os.api('i/get-word-muted-notes-count', {}).then(response => {
-	hardWordMutedNotesCount.value = response?.count;
-});
-
-watch(softMutedWords, () => {
-	changed.value = true;
-});
-
-watch(hardMutedWords, () => {
+watch(mutedWords, () => {
 	changed.value = true;
 });
 
 async function save() {
-	const parseMutes = (mutes, tab) => {
+	const parseMutes = (mutes) => {
 		// split into lines, remove empty lines and unnecessary whitespace
 		let lines = mutes.trim().split('\n').map(line => line.trim()).filter(line => line !== '');
 
@@ -92,7 +64,7 @@ async function save() {
 					os.alert({
 						type: 'error',
 						title: i18n.ts.regexpError,
-						text: i18n.t('regexpErrorDescription', { tab, line: i + 1 }) + '\n' + err.toString(),
+						text: i18n.t('regexpErrorDescription', { tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
 					});
 					// re-throw error so these invalid settings are not saved
 					throw err;
@@ -105,29 +77,18 @@ async function save() {
 		return lines;
 	};
 
-	let softMutes, hardMutes;
+	let parsed;
 	try {
-		softMutes = parseMutes(softMutedWords.value, i18n.ts._wordMute.soft);
-		hardMutes = parseMutes(hardMutedWords.value, i18n.ts._wordMute.hard);
+		parsed = parseMutes(mutedWords.value);
 	} catch (err) {
 		// already displayed error message in parseMutes
 		return;
 	}
 
-	defaultStore.set('mutedWords', softMutes);
 	await os.api('i/update', {
-		mutedWords: hardMutes,
+		mutedWords: parsed,
 	});
 
 	changed.value = false;
 }
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata({
-	title: i18n.ts.wordMute,
-	icon: 'ti ti-message-off',
-});
 </script>
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index b6ac17ed4f9f5f8219cdf1dcb49c7a11b35a0bff..819e7ffe53ae16debbc026df219848afd8393a87 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -38,14 +38,12 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const masterVolume = computed(soundConfigStore.makeGetterSetter('sound_masterVolume'));
 
-const soundsKeys = ['note', 'noteMy', 'notification', 'chat', 'chatBg', 'antenna', 'channel'] as const;
+const soundsKeys = ['note', 'noteMy', 'notification', 'antenna', 'channel'] as const;
 
 const sounds = ref<Record<typeof soundsKeys[number], Ref<any>>>({
 	note: soundConfigStore.reactiveState.sound_note,
 	noteMy: soundConfigStore.reactiveState.sound_noteMy,
 	notification: soundConfigStore.reactiveState.sound_notification,
-	chat: soundConfigStore.reactiveState.sound_chat,
-	chatBg: soundConfigStore.reactiveState.sound_chatBg,
 	antenna: soundConfigStore.reactiveState.sound_antenna,
 	channel: soundConfigStore.reactiveState.sound_channel,
 });
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 5bad689aee250c02b89ab6a699aeab8473b32bc3..b8deb779524146995c79624ecc1e0bcd88f3a685 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -15,11 +15,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div :class="$style.tl">
 				<MkTimeline
 					ref="tlComponent"
-					:key="src + withRenotes + withReplies + onlyFiles"
+					:key="src + withRenotes + onlyFiles"
 					:src="src.split(':')[0]"
 					:list="src.split(':')[1]"
 					:withRenotes="withRenotes"
-					:withReplies="withReplies"
 					:onlyFiles="onlyFiles"
 					:sound="true"
 					@queue="queueUpdated"
@@ -62,7 +61,6 @@ let queue = $ref(0);
 let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
 const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
 const withRenotes = $ref(true);
-const withReplies = $ref(false);
 const onlyFiles = $ref(false);
 
 watch($$(src), () => queue = 0);
@@ -144,11 +142,6 @@ const headerActions = $computed(() => [{
 			text: i18n.ts.showRenotes,
 			icon: 'ti ti-repeat',
 			ref: $$(withRenotes),
-		}, {
-			type: 'switch',
-			text: i18n.ts.withReplies,
-			icon: 'ti ti-arrow-back-up',
-			ref: $$(withReplies),
 		}, {
 			type: 'switch',
 			text: i18n.ts.fileAttachedOnly,
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 83244876fe795f0a0fb6e345a8ff11da5e842027..0fc7b62d827270a55f18a3dfde8907d30cbe7173 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -61,20 +61,7 @@ function settings() {
 	router.push(`/my/lists/${props.listId}`);
 }
 
-async function timetravel() {
-	const { canceled, result: date } = await os.inputDate({
-		title: i18n.ts.date,
-	});
-	if (canceled) return;
-
-	tlEl.timetravel(date);
-}
-
 const headerActions = $computed(() => list ? [{
-	icon: 'ti ti-calendar-time',
-	text: i18n.ts.jumpToSpecifiedDate,
-	handler: timetravel,
-}, {
 	icon: 'ti ti-settings',
 	text: i18n.ts.settings,
 	handler: settings,
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 385c81a97ffbcce6a5e06ba1cee4c8ba51af5c38..605e9fbb76390c904c1668a5c36e558ebb1bfc2d 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -128,14 +128,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 				<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
 				<template v-if="narrow">
-					<XPhotos :key="user.id" :user="user"/>
+					<XFiles :key="user.id" :user="user"/>
 					<XActivity :key="user.id" :user="user"/>
 				</template>
-				<MkNotes v-if="!disableNotes" :class="$style.tl" :noGap="true" :pagination="pagination"/>
+				<div v-if="!disableNotes">
+					<div style="margin-bottom: 8px;">{{ i18n.ts.featured }}</div>
+					<MkNotes :class="$style.tl" :noGap="true" :pagination="pagination"/>
+				</div>
 			</div>
 		</div>
 		<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
-			<XPhotos :key="user.id" :user="user"/>
+			<XFiles :key="user.id" :user="user"/>
 			<XActivity :key="user.id" :user="user"/>
 		</div>
 	</div>
@@ -182,7 +185,7 @@ function calcAge(birthdate: string): number {
 	return yearDiff;
 }
 
-const XPhotos = defineAsyncComponent(() => import('./index.photos.vue'));
+const XFiles = defineAsyncComponent(() => import('./index.files.vue'));
 const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
 
 const props = withDefaults(defineProps<{
@@ -210,7 +213,7 @@ watch($$(moderationNote), async () => {
 });
 
 const pagination = {
-	endpoint: 'users/notes' as const,
+	endpoint: 'users/featured-notes' as const,
 	limit: 10,
 	params: computed(() => ({
 		userId: props.user.id,
diff --git a/packages/frontend/src/pages/user/index.photos.vue b/packages/frontend/src/pages/user/index.files.vue
similarity index 63%
rename from packages/frontend/src/pages/user/index.photos.vue
rename to packages/frontend/src/pages/user/index.files.vue
index b6cae9f13109a86fc2982856ee261acf079bda02..43d6d91fc9703f556fb11a62f1af996a4e97c6f3 100644
--- a/packages/frontend/src/pages/user/index.photos.vue
+++ b/packages/frontend/src/pages/user/index.files.vue
@@ -6,20 +6,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <MkContainer :max-height="300" :foldable="true">
 	<template #icon><i class="ti ti-photo"></i></template>
-	<template #header>{{ i18n.ts.images }}</template>
+	<template #header>{{ i18n.ts.files }}</template>
 	<div :class="$style.root">
 		<MkLoading v-if="fetching"/>
-		<div v-if="!fetching && images.length > 0" :class="$style.stream">
-			<MkA
-				v-for="image in images"
-				:key="image.note.id + image.file.id"
-				:class="$style.img"
-				:to="notePage(image.note)"
-			>
-				<ImgWithBlurhash :hash="image.file.blurhash" :src="thumbnail(image.file)" :title="image.file.name"/>
-			</MkA>
+		<div v-if="!fetching && files.length > 0" :class="$style.stream">
+			<template v-for="file in files" :key="file.note.id + file.file.id">
+				<div v-if="file.file.isSensitive && !showingFiles.includes(file.file.id)" :class="$style.sensitive" @click="showingFiles.push(file.file.id)">
+					<div>
+						<div><i class="ti ti-eye-exclamation"></i> {{ i18n.ts.sensitive }}</div>
+						<div>{{ i18n.ts.clickToShow }}</div>
+					</div>
+				</div>
+				<MkA v-else :class="$style.img" :to="notePage(file.note)">
+					<!-- TODO: 画像以外のファイルに対応 -->
+					<ImgWithBlurhash :hash="file.file.blurhash" :src="thumbnail(file.file)" :title="file.file.name"/>
+				</MkA>
+			</template>
 		</div>
-		<p v-if="!fetching && images.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
+		<p v-if="!fetching && files.length == 0" :class="$style.empty">{{ i18n.ts.nothing }}</p>
 	</div>
 </MkContainer>
 </template>
@@ -40,10 +44,11 @@ const props = defineProps<{
 }>();
 
 let fetching = $ref(true);
-let images = $ref<{
+let files = $ref<{
 	note: Misskey.entities.Note;
 	file: Misskey.entities.DriveFile;
 }[]>([]);
+let showingFiles = $ref<string[]>([]);
 
 function thumbnail(image: Misskey.entities.DriveFile): string {
 	return defaultStore.state.disableShowingAnimatedImages
@@ -52,24 +57,15 @@ function thumbnail(image: Misskey.entities.DriveFile): string {
 }
 
 onMounted(() => {
-	const image = [
-		'image/jpeg',
-		'image/webp',
-		'image/avif',
-		'image/png',
-		'image/gif',
-		'image/apng',
-		'image/vnd.mozilla.apng',
-	];
 	os.api('users/notes', {
 		userId: props.user.id,
-		fileType: image,
+		withFiles: true,
 		excludeNsfw: defaultStore.state.nsfw !== 'ignore',
-		limit: 10,
+		limit: 15,
 	}).then(notes => {
 		for (const note of notes) {
 			for (const file of note.files) {
-				images.push({
+				files.push({
 					note,
 					file,
 				});
@@ -102,4 +98,9 @@ onMounted(() => {
 	padding: 16px;
 	text-align: center;
 }
+
+.sensitive {
+	display: grid;
+  place-items: center;
+}
 </style>
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index 42040f5304cc44b37bc4099f59ed236869db1f18..724fb4d11c7304d595c6d45d008095cc53e3a519 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -29,7 +29,7 @@ const props = defineProps<{
 	user: Misskey.entities.UserDetailed;
 }>();
 
-const include = ref<string | null>(null);
+const include = ref<string | null>('all');
 
 const pagination = {
 	endpoint: 'users/notes' as const,
@@ -38,6 +38,7 @@ const pagination = {
 		userId: props.user.id,
 		withRenotes: include.value === 'all',
 		withReplies: include.value === 'all' || include.value === 'files',
+		withChannelNotes: include.value === 'all',
 		withFiles: include.value === 'files',
 	})),
 };
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 415d2f19742202a085467f34e12d8a783b1895f7..6c33d0d8ee91acbe2a207dc291420e23aa76c2d6 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -126,18 +126,10 @@ export const routes = [{
 		path: '/import-export',
 		name: 'import-export',
 		component: page(() => import('./pages/settings/import-export.vue')),
-	}, {
-		path: '/instance-mute',
-		name: 'instance-mute',
-		component: page(() => import('./pages/settings/instance-mute.vue')),
 	}, {
 		path: '/mute-block',
 		name: 'mute-block',
 		component: page(() => import('./pages/settings/mute-block.vue')),
-	}, {
-		path: '/word-mute',
-		name: 'word-mute',
-		component: page(() => import('./pages/settings/word-mute.vue')),
 	}, {
 		path: '/api',
 		name: 'api',
@@ -435,6 +427,10 @@ export const routes = [{
 		path: '/proxy-account',
 		name: 'proxy-account',
 		component: page(() => import('./pages/admin/proxy-account.vue')),
+	}, {
+		path: '/external-services',
+		name: 'external-services',
+		component: page(() => import('./pages/admin/external-services.vue')),
 	}, {
 		path: '/other-settings',
 		name: 'other-settings',
@@ -471,6 +467,10 @@ export const routes = [{
 	path: '/my/drive',
 	component: page(() => import('./pages/drive.vue')),
 	loginRequired: true,
+}, {
+	path: '/my/drive/file/:fileId',
+	component: page(() => import('./pages/drive.file.vue')),
+	loginRequired: true,
 }, {
 	path: '/my/follow-requests',
 	component: page(() => import('./pages/follow-requests.vue')),
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index 0964108249940f51d3da11d706e82badd25dd00c..8b2144a22f65e0a423543bcfd8abb15b14e31c2e 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -27,7 +27,7 @@ function rename(file: Misskey.entities.DriveFile) {
 
 function describe(file: Misskey.entities.DriveFile) {
 	os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), {
-		default: file.comment != null ? file.comment : '',
+		default: file.comment ?? '',
 		file: file,
 	}, {
 		done: caption => {
@@ -112,6 +112,11 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
 		text: i18n.ts.download,
 		icon: 'ti ti-download',
 		download: file.name,
+	}, null, {
+		type: 'link',
+		to: `/my/drive/file/${file.id}`,
+		text: i18n.ts._fileViewer.title,
+		icon: 'ti ti-file',
 	}, null, {
 		text: i18n.ts.delete,
 		icon: 'ti ti-trash',
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index 62d9aab91bd61ef41c6cca3186b98b5114b0815a..e399145fc9552ca687ee47d4d5b73d4ea3d0451d 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -172,10 +172,6 @@ export function getNoteMenu(props: {
 		});
 	}
 
-	function edit(): void {
-		os.post({ initialNote: appearNote, renote: appearNote.renote, reply: appearNote.reply, channel: appearNote.channel, updateMode: true });
-	}
-
 	function toggleFavorite(favorite: boolean): void {
 		claimAchievement('noteFavorited1');
 		os.apiWithDialog(favorite ? 'notes/favorites/create' : 'notes/favorites/delete', {
@@ -356,11 +352,6 @@ export function getNoteMenu(props: {
 			),
 			...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
 				null,
-				appearNote.userId === $i.id && $i.policies.canEditNote ? {
-					icon: 'ti ti-edit',
-					text: i18n.ts.edit,
-					action: edit,
-				} : undefined,
 				appearNote.userId === $i.id ? {
 					icon: 'ti ti-edit',
 					text: i18n.ts.deleteAndEdit,
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 128cbafb1568d42cf4c2928edffa1ece41247cbf..be514be5b1f6999f32e33abcb3d974099891e9d3 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -80,6 +80,15 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 		});
 	}
 
+	async function toggleWithReplies() {
+		os.apiWithDialog('following/update', {
+			userId: user.id,
+			withReplies: !user.withReplies,
+		}).then(() => {
+			user.withReplies = !user.withReplies;
+		});
+	}
+
 	async function toggleNotify() {
 		os.apiWithDialog('following/update', {
 			userId: user.id,
@@ -282,6 +291,10 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 		// フォローしたとしても user.isFollowing はリアルタイム更新されないので不便なため
 		//if (user.isFollowing) {
 		menu = menu.concat([{
+			icon: user.withReplies ? 'ti ti-messages-off' : 'ti ti-messages',
+			text: user.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
+			action: toggleWithReplies,
+		}, {
 			icon: user.notify === 'none' ? 'ti ti-bell' : 'ti ti-bell-off',
 			text: user.notify === 'none' ? i18n.ts.notifyNotes : i18n.ts.unnotifyNotes,
 			action: toggleNotify,
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index 1ef41b47d35493fddc5cb17378ac8048e1665760..1ef075818ffb58eefe0f497c6c50187688617323 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -7,10 +7,6 @@ import { markRaw } from 'vue';
 import { Storage } from '@/pizzax.js';
 
 export const soundConfigStore = markRaw(new Storage('sound', {
-	mediaVolume: {
-		where: 'device',
-		default: 0.5,
-	},
 	sound_masterVolume: {
 		where: 'device',
 		default: 0.3,
@@ -27,14 +23,6 @@ export const soundConfigStore = markRaw(new Storage('sound', {
 		where: 'account',
 		default: { type: 'syuilo/n-ea', volume: 1 },
 	},
-	sound_chat: {
-		where: 'account',
-		default: { type: 'syuilo/pope1', volume: 1 },
-	},
-	sound_chatBg: {
-		where: 'account',
-		default: { type: 'syuilo/waon', volume: 1 },
-	},
 	sound_antenna: {
 		where: 'account',
 		default: { type: 'syuilo/triple', volume: 1 },
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index 1c924e774f710a80beda07a1da443a57c6d6a82f..b6383487c92d15047981bfcd37cfedde6b050be2 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -5,7 +5,11 @@
 
 import { ref } from 'vue';
 import tinycolor from 'tinycolor2';
-import { globalEvents } from '@/events';
+import { deepClone } from './clone.js';
+import { globalEvents } from '@/events.js';
+import lightTheme from '@/themes/_light.json5';
+import darkTheme from '@/themes/_dark.json5';
+import { miLocalStorage } from '@/local-storage.js';
 
 export type Theme = {
 	id: string;
@@ -16,11 +20,6 @@ export type Theme = {
 	props: Record<string, string>;
 };
 
-import lightTheme from '@/themes/_light.json5';
-import darkTheme from '@/themes/_dark.json5';
-import { deepClone } from './clone';
-import { miLocalStorage } from '@/local-storage.js';
-
 export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
 
 export const getBuiltinThemes = () => Promise.all(
@@ -101,18 +100,11 @@ export function applyTheme(theme: Theme, persist = true) {
 
 function compile(theme: Theme): Record<string, string> {
 	function getColor(val: string): tinycolor.Instance {
-		// ref (prop)
-		if (val[0] === '@') {
+		if (val[0] === '@') { // ref (prop)
 			return getColor(theme.props[val.substring(1)]);
-		}
-
-		// ref (const)
-		else if (val[0] === '$') {
+		} else if (val[0] === '$') { // ref (const)
 			return getColor(theme.props[val]);
-		}
-
-		// func
-		else if (val[0] === ':') {
+		} else if (val[0] === ':') { // func
 			const parts = val.split('<');
 			const func = parts.shift().substring(1);
 			const arg = parseFloat(parts.shift());
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index a4c913749ee5fa52bf456c46935c202e0664a082..c6185325706539a2a126de00c9ac90ffef935a3c 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -71,13 +71,6 @@ export function useNoteCapture(props: {
 				break;
 			}
 
-			case 'updated': {
-				note.value.updatedAt = new Date().toISOString();
-				note.value.cw = body.cw;
-				note.value.text = body.text;
-				break;
-			}
-
 			case 'deleted': {
 				props.isDeletedRef.value = true;
 				break;
diff --git a/packages/frontend/src/scripts/use-tooltip.ts b/packages/frontend/src/scripts/use-tooltip.ts
index 0a82997728b17e851988797c7283813b218e4793..17ea380db06cbe36f5447279ef32c3d1432f17f7 100644
--- a/packages/frontend/src/scripts/use-tooltip.ts
+++ b/packages/frontend/src/scripts/use-tooltip.ts
@@ -21,6 +21,8 @@ export function useTooltip(
 
 	let changeShowingState: (() => void) | null;
 
+	let autoHidingTimer;
+
 	const open = () => {
 		close();
 		if (!isHovering) return;
@@ -33,6 +35,16 @@ export function useTooltip(
 		changeShowingState = () => {
 			showing.value = false;
 		};
+
+		autoHidingTimer = window.setInterval(() => {
+			if (!document.body.contains(elRef.value)) {
+				if (!isHovering) return;
+				isHovering = false;
+				window.clearTimeout(timeoutId);
+				close();
+				window.clearInterval(autoHidingTimer);
+			}
+		}, 1000);
 	};
 
 	const close = () => {
@@ -53,6 +65,7 @@ export function useTooltip(
 		if (!isHovering) return;
 		isHovering = false;
 		window.clearTimeout(timeoutId);
+		window.clearInterval(autoHidingTimer);
 		close();
 	};
 
@@ -67,6 +80,7 @@ export function useTooltip(
 		if (!isHovering) return;
 		isHovering = false;
 		window.clearTimeout(timeoutId);
+		window.clearInterval(autoHidingTimer);
 		close();
 	};
 
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index e715088d03c96ba3a769d7c2fb9f2c74ae70ec25..58730c7cef044260092420c39045d8c0908567de 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -5,7 +5,7 @@
 
 import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { miLocalStorage } from './local-storage';
+import { miLocalStorage } from './local-storage.js';
 import { Storage } from '@/pizzax.js';
 
 interface PostFormAction {
@@ -101,10 +101,6 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
 	},
-	mutedWords: {
-		where: 'account',
-		default: [],
-	},
 	mutedAds: {
 		where: 'account',
 		default: [] as string[],
diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5
index 5ef6adb085e53dbac4178fcab63898c2ecfd9868..3f5822977abcb6d978b37de025cdab8c00449c68 100644
--- a/packages/frontend/src/themes/_dark.json5
+++ b/packages/frontend/src/themes/_dark.json5
@@ -54,9 +54,6 @@
 		infoWarnBg: '#42321c',
 		infoWarnFg: '#ffbd3e',
 		switchBg: 'rgba(255, 255, 255, 0.15)',
-		cwBg: '#687390',
-		cwFg: '#393f4f',
-		cwHoverBg: '#707b97',
 		buttonBg: 'rgba(255, 255, 255, 0.05)',
 		buttonHoverBg: 'rgba(255, 255, 255, 0.1)',
 		buttonGradateA: '@accent',
diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5
index 32f3c7490917f59df4783c8c6397e5d438fb365f..6ebfcaafeb5319b3b557ff02e83af2c3ebaad587 100644
--- a/packages/frontend/src/themes/_light.json5
+++ b/packages/frontend/src/themes/_light.json5
@@ -54,9 +54,6 @@
 		infoWarnBg: '#fff0db',
 		infoWarnFg: '#8f6e31',
 		switchBg: 'rgba(0, 0, 0, 0.15)',
-		cwBg: '#b1b9c1',
-		cwFg: '#fff',
-		cwHoverBg: '#bbc4ce',
 		buttonBg: 'rgba(0, 0, 0, 0.05)',
 		buttonHoverBg: 'rgba(0, 0, 0, 0.1)',
 		buttonGradateA: '@accent',
diff --git a/packages/frontend/src/themes/d-astro.json5 b/packages/frontend/src/themes/d-astro.json5
index 09a9ead1a2e3398fc367e2dc615ece71c80cdf80..fee25cc4a4205d5b2c40b827650b8c1cadbaa720 100644
--- a/packages/frontend/src/themes/d-astro.json5
+++ b/packages/frontend/src/themes/d-astro.json5
@@ -6,8 +6,6 @@
 	props: {
 		bg: '#232125',
 		fg: '#efdab9',
-		cwBg: '#687390',
-		cwFg: '#393f4f',
 		link: '#78b0a0',
 		warn: '#ecb637',
 		badge: '#31b1ce',
@@ -29,7 +27,6 @@
 		success: '#86b300',
 		buttonBg: 'rgba(255, 255, 255, 0.05)',
 		acrylicBg: ':alpha<0.5<@bg',
-		cwHoverBg: '#707b97',
 		indicator: '@accent',
 		mentionMe: '#fb5d38',
 		messageBg: '@bg',
diff --git a/packages/frontend/src/themes/d-u0.json5 b/packages/frontend/src/themes/d-u0.json5
index ed776746a88a7238259386d38d3cfd6f9d644ed8..3bd0b9483c3df1aee7ed7f1004539acde9062e65 100644
--- a/packages/frontend/src/themes/d-u0.json5
+++ b/packages/frontend/src/themes/d-u0.json5
@@ -21,8 +21,6 @@
 		X15: ':alpha<0<@panel',
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
-		cwBg: '#687390',
-		cwFg: '#393f4f',
 		link: '@accent',
 		warn: '#ecb637',
 		badge: '#31b1ce',
@@ -46,7 +44,6 @@
 		buttonBg: 'rgba(255, 255, 255, 0.05)',
 		switchBg: 'rgba(255, 255, 255, 0.15)',
 		acrylicBg: ':alpha<0.5<@bg',
-		cwHoverBg: '#707b97',
 		indicator: '@accent',
 		mentionMe: '@mention',
 		messageBg: '@bg',
diff --git a/packages/frontend/src/themes/l-u0.json5 b/packages/frontend/src/themes/l-u0.json5
index b77b15e3f0ad1c9d3fbb64d6976fd9d7a35acf51..dbc777d49368107c638f60e7368e1c3b00563c8c 100644
--- a/packages/frontend/src/themes/l-u0.json5
+++ b/packages/frontend/src/themes/l-u0.json5
@@ -21,8 +21,6 @@
 		X15: ':alpha<0<@panel',
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
-		cwBg: '#687390',
-		cwFg: '#393f4f',
 		link: '@accent',
 		warn: '#ecb637',
 		badge: '#31b1ce',
@@ -46,7 +44,6 @@
 		buttonBg: '#0000000d',
 		switchBg: 'rgba(255, 255, 255, 0.15)',
 		acrylicBg: ':alpha<0.5<@bg',
-		cwHoverBg: '#707b97',
 		indicator: '@accent',
 		mentionMe: '@mention',
 		messageBg: '@bg',
diff --git a/packages/frontend/src/themes/l-vivid.json5 b/packages/frontend/src/themes/l-vivid.json5
index 822ef948dd8c912b1119f178d693d6f16c4929e9..3368855b5e96896449a95c6227cc3e87862551d4 100644
--- a/packages/frontend/src/themes/l-vivid.json5
+++ b/packages/frontend/src/themes/l-vivid.json5
@@ -9,8 +9,6 @@
 	props: {
 		bg: '#fafafa',
 		fg: '#444',
-		cwBg: '#b1b9c1',
-		cwFg: '#fff',
 		link: '#ff9400',
 		warn: '#ecb637',
 		badge: '#31b1ce',
@@ -32,7 +30,6 @@
 		success: '#86b300',
 		buttonBg: 'rgba(0, 0, 0, 0.05)',
 		acrylicBg: ':alpha<0.5<@bg',
-		cwHoverBg: '#bbc4ce',
 		indicator: '@accent',
 		mentionMe: '@mention',
 		messageBg: '@bg',
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index ca4a71a67f0fd4078856d4ea45a3bdc207a792a6..e075e05db367d93d30e07dea62b85f13c4b3ebaf 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -68,7 +68,25 @@ export function openInstanceMenu(ev: MouseEvent) {
 			text: i18n.ts.manageCustomEmojis,
 			icon: 'ti ti-icons',
 		} : undefined],
-	}, null, {
+	}, null, (instance.impressumUrl) ? {
+		text: i18n.ts.impressum,
+		icon: 'ti ti-file-invoice',
+		action: () => {
+			window.open(instance.impressumUrl, '_blank');
+		},
+	} : undefined, (instance.tosUrl) ? {
+		text: i18n.ts.termsOfService,
+		icon: 'ti ti-notebook',
+		action: () => {
+			window.open(instance.tosUrl, '_blank');
+		},
+	} : undefined, (instance.privacyPolicyUrl) ? {
+		text: i18n.ts.privacyPolicy,
+		icon: 'ti ti-shield-lock',
+		action: () => {
+			window.open(instance.privacyPolicyUrl, '_blank');
+		},
+	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : null, {
 		text: i18n.ts.help,
 		icon: 'ti ti-help-circle',
 		action: () => {
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index 49fdf4d314659bd1a367a36479b9bd89da90aa97..b2a44ac96b12aae611b5e196adeae93613ffe651 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -31,7 +31,6 @@ export type Column = {
 	excludeTypes?: typeof notificationTypes[number][];
 	tl?: 'home' | 'local' | 'social' | 'global';
 	withRenotes?: boolean;
-	withReplies?: boolean;
 	onlyFiles?: boolean;
 };
 
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index c43ccdfabb4ec087cfa4eba1a1be93d958fc00b9..14bc6917a35d36eb09c3dd9c1b4d041ab9d63702 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -9,12 +9,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span>
 	</template>
 
-	<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId"/>
+	<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" :withRenotes="withRenotes"/>
 </XColumn>
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { watch } from 'vue';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store';
 import MkTimeline from '@/components/MkTimeline.vue';
@@ -27,11 +27,18 @@ const props = defineProps<{
 }>();
 
 let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
+const withRenotes = $ref(props.column.withRenotes ?? true);
 
 if (props.column.listId == null) {
 	setList();
 }
 
+watch($$(withRenotes), v => {
+	updateColumn(props.column.id, {
+		withRenotes: v,
+	});
+});
+
 async function setList() {
 	const lists = await os.api('users/lists/list');
 	const { canceled, result: list } = await os.select({
@@ -62,5 +69,10 @@ const menu = [
 		text: i18n.ts.editList,
 		action: editList,
 	},
+	{
+		type: 'switch',
+		text: i18n.ts.showRenotes,
+		ref: $$(withRenotes),
+	},
 ];
 </script>
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index aad73d73a1e3929abaf216ba42b5c12336383a0a..847752247e982ee029c8cc142a36f9a9d0e6b106 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -23,10 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkTimeline
 		v-else-if="column.tl"
 		ref="timeline"
-		:key="column.tl + withRenotes + withReplies + onlyFiles"
+		:key="column.tl + withRenotes + onlyFiles"
 		:src="column.tl"
 		:withRenotes="withRenotes"
-		:withReplies="withReplies"
 		:onlyFiles="onlyFiles"
 	/>
 </XColumn>
@@ -52,7 +51,6 @@ let disabled = $ref(false);
 const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
 const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
 const withRenotes = $ref(props.column.withRenotes ?? true);
-const withReplies = $ref(props.column.withReplies ?? false);
 const onlyFiles = $ref(props.column.onlyFiles ?? false);
 
 watch($$(withRenotes), v => {
@@ -61,12 +59,6 @@ watch($$(withRenotes), v => {
 	});
 });
 
-watch($$(withReplies), v => {
-	updateColumn(props.column.id, {
-		withReplies: v,
-	});
-});
-
 watch($$(onlyFiles), v => {
 	updateColumn(props.column.id, {
 		onlyFiles: v,
@@ -115,10 +107,6 @@ const menu = [{
 	type: 'switch',
 	text: i18n.ts.showRenotes,
 	ref: $$(withRenotes),
-}, {
-	type: 'switch',
-	text: i18n.ts.withReplies,
-	ref: $$(withReplies),
 }, {
 	type: 'switch',
 	text: i18n.ts.fileAttachedOnly,
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index f0fc47c2073e6f910999cd915bf4d95111d0d8ce..1a0bbeac78ea3b2c08f7a420a6b8ca1efabe619c 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1381,10 +1381,6 @@ export type Endpoints = {
         req: TODO;
         res: TODO;
     };
-    'i/get-word-muted-notes-count': {
-        req: TODO;
-        res: TODO;
-    };
     'i/import-following': {
         req: TODO;
         res: TODO;
@@ -2412,6 +2408,8 @@ type LiteInstanceMetadata = {
     tosUrl: string | null;
     repositoryUrl: string;
     feedbackUrl: string;
+    impressumUrl: string | null;
+    privacyPolicyUrl: string | null;
     disableRegistration: boolean;
     disableLocalTimeline: boolean;
     disableGlobalTimeline: boolean;
@@ -2450,6 +2448,7 @@ type LiteInstanceMetadata = {
         url: string;
         imageUrl: string;
     }[];
+    notesPerOneAd: number;
     translatorAvailable: boolean;
     serverRules: string[];
 };
@@ -2643,7 +2642,6 @@ export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];
 type Note = {
     id: ID;
     createdAt: DateString;
-    updatedAt?: DateString | null;
     text: string | null;
     cw: string | null;
     user: User;
@@ -2757,6 +2755,9 @@ type Notification_2 = {
     invitation: UserGroup;
     user: User;
     userId: User['id'];
+} | {
+    type: 'achievementEarned';
+    achievement: string;
 } | {
     type: 'app';
     header?: string | null;
@@ -2767,7 +2768,7 @@ type Notification_2 = {
 });
 
 // @public (undocumented)
-export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app"];
+export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "achievementEarned"];
 
 // @public (undocumented)
 type OriginType = 'combined' | 'local' | 'remote';
@@ -2981,9 +2982,9 @@ type UserSorting = '+follower' | '-follower' | '+createdAt' | '-createdAt' | '+u
 //
 // src/api.types.ts:16:32 - (ae-forgotten-export) The symbol "TODO" needs to be exported by the entry point index.d.ts
 // src/api.types.ts:18:25 - (ae-forgotten-export) The symbol "NoParams" needs to be exported by the entry point index.d.ts
-// src/api.types.ts:631:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
+// src/api.types.ts:630:18 - (ae-forgotten-export) The symbol "ShowUserReq" needs to be exported by the entry point index.d.ts
 // src/entities.ts:107:2 - (ae-forgotten-export) The symbol "notificationTypes_2" needs to be exported by the entry point index.d.ts
-// src/entities.ts:595:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
+// src/entities.ts:600:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts
 // src/streaming.types.ts:33:4 - (ae-forgotten-export) The symbol "FIXME" needs to be exported by the entry point index.d.ts
 
 // (No @packageDocumentation comment for this package)
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index cdbb5f38ce41a0349589a3dc3bc5d3ffcb42b1e6..1b27380019f5eac46aece380bba8116950d92efb 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -20,13 +20,13 @@
 		"url": "git+https://github.com/misskey-dev/misskey.js.git"
 	},
 	"devDependencies": {
-		"@microsoft/api-extractor": "7.37.2",
+		"@microsoft/api-extractor": "7.38.0",
 		"@swc/jest": "0.2.29",
 		"@types/jest": "29.5.5",
-		"@types/node": "20.7.1",
-		"@typescript-eslint/eslint-plugin": "6.7.3",
-		"@typescript-eslint/parser": "6.7.3",
-		"eslint": "8.50.0",
+		"@types/node": "20.8.4",
+		"@typescript-eslint/eslint-plugin": "6.7.5",
+		"@typescript-eslint/parser": "6.7.5",
+		"eslint": "8.51.0",
 		"jest": "29.7.0",
 		"jest-fetch-mock": "3.0.3",
 		"jest-websocket-mock": "2.5.0",
@@ -39,7 +39,7 @@
 	],
 	"dependencies": {
 		"@swc/cli": "0.1.62",
-		"@swc/core": "1.3.90",
+		"@swc/core": "1.3.92",
 		"eventemitter3": "5.0.1",
 		"reconnecting-websocket": "4.4.0"
 	}
diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts
index 974cb35ace60738fa8675859093797d4cb21d36e..9415e692e37bffe0ba839c163df5729c7d515ff5 100644
--- a/packages/misskey-js/src/api.ts
+++ b/packages/misskey-js/src/api.ts
@@ -67,8 +67,7 @@ export class APIClient {
 			IsCaseMatched<E, P, 8> extends true ? GetCaseResult<E, P, 8> :
 			IsCaseMatched<E, P, 9> extends true ? GetCaseResult<E, P, 9> :
 			Endpoints[E]['res']['$switch']['$default']
-		: Endpoints[E]['res']>
-	{
+		: Endpoints[E]['res']> {
 		const promise = new Promise((resolve, reject) => {
 			this.fetch(`${this.origin}/api/${endpoint}`, {
 				method: 'POST',
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index e69d8324a1cbbf92c5566a52544c35234c5f7487..a7a2ea1b36ee75cd75a0caef39dde87bd63a7e1c 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -371,7 +371,6 @@ export type Endpoints = {
 	'i/favorites': { req: { limit?: number; sinceId?: NoteFavorite['id']; untilId?: NoteFavorite['id']; }; res: NoteFavorite[]; };
 	'i/gallery/likes': { req: TODO; res: TODO; };
 	'i/gallery/posts': { req: TODO; res: TODO; };
-	'i/get-word-muted-notes-count': { req: TODO; res: TODO; };
 	'i/import-following': { req: TODO; res: TODO; };
 	'i/import-user-lists': { req: TODO; res: TODO; };
 	'i/move': { req: TODO; res: TODO; };
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 271a64274ff9c85f8c342aefa54472c939ae1bb0..c4ddead823aed2570f097de7ff959877a8952d46 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -1,4 +1,4 @@
-export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app'] as const;
+export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'achievementEarned'] as const;
 
 export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
 
@@ -189,6 +189,9 @@ export type ModerationLogPayloads = {
 	deleteUserAnnouncement: {
 		announcementId: string;
 		announcement: any;
+		userId: string;
+		userUsername: string;
+		userHost: string | null;
 	};
 	resetPassword: {
 		userId: string;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index e6bac2a5f46f972a6619c393293e2568f83de9fa..aed242d8aa8b78c07dec9fa2340f1b52ab0fdca1 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -177,7 +177,6 @@ export type GalleryPost = {
 export type Note = {
 	id: ID;
 	createdAt: DateString;
-	updatedAt?: DateString | null;
 	text: string | null;
 	cw: string | null;
 	user: User;
@@ -278,6 +277,9 @@ export type Notification = {
 	invitation: UserGroup;
 	user: User;
 	userId: User['id'];
+} | {
+	type: 'achievementEarned';
+	achievement: string;
 } | {
 	type: 'app';
 	header?: string | null;
@@ -323,6 +325,8 @@ export type LiteInstanceMetadata = {
 	tosUrl: string | null;
 	repositoryUrl: string;
 	feedbackUrl: string;
+	impressumUrl: string | null;
+	privacyPolicyUrl: string | null;
 	disableRegistration: boolean;
 	disableLocalTimeline: boolean;
 	disableGlobalTimeline: boolean;
@@ -361,6 +365,7 @@ export type LiteInstanceMetadata = {
 		url: string;
 		imageUrl: string;
 	}[];
+	notesPerOneAd: number;
 	translatorAvailable: boolean;
 	serverRules: string[];
 };
diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts
index ce29a000329aa2cb273065f9530730d9c852cae8..96ac7787e1e18415850400a8fde41c0fafee833f 100644
--- a/packages/misskey-js/src/streaming.types.ts
+++ b/packages/misskey-js/src/streaming.types.ts
@@ -133,13 +133,6 @@ export type NoteUpdatedEvent = {
 	body: {
 		deletedAt: string;
 	};
-} | {
-	id: Note['id'];
-	type: 'updated';
-	body: {
-		cw: string | null;
-		text: string;
-	};
 } | {
 	id: Note['id'];
 	type: 'pollVoted';
diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js
index 1ecad7ab759ddf586528ee1eecbda4c50fa03256..c578894f60f8ef008c30cdee3ef43af45c21972d 100644
--- a/packages/shared/.eslintrc.js
+++ b/packages/shared/.eslintrc.js
@@ -38,6 +38,9 @@ module.exports = {
 			'before': true,
 			'after': true,
 		}],
+		'brace-style': ['error', '1tbs', {
+			'allowSingleLine': true,
+		}],
 		'padded-blocks': ['error', 'never'],
 		/* TODO: path aliasを使わないとwarnする
 		'no-restricted-imports': ['warn', {
diff --git a/packages/sw/package.json b/packages/sw/package.json
index 4499e9f38e2c27a84de52db94cb4430739c9d5e8..24878a6e2f927108f2e822a97bd23440afba025c 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -14,9 +14,9 @@
 		"misskey-js": "workspace:*"
 	},
 	"devDependencies": {
-		"@typescript-eslint/parser": "6.7.3",
+		"@typescript-eslint/parser": "6.7.5",
 		"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
-		"eslint": "8.50.0",
+		"eslint": "8.51.0",
 		"eslint-plugin-import": "2.28.1",
 		"typescript": "5.2.2"
 	},
diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts
index be4f066b5fe8902753a0b25c9d6ba842d2993362..0cbf4c7953dacb4d639bb79197bf1fb209223c85 100644
--- a/packages/sw/src/scripts/operations.ts
+++ b/packages/sw/src/scripts/operations.ts
@@ -15,7 +15,7 @@ import { getUrlWithLoginId } from '@/scripts/login-id.js';
 export const cli = new Misskey.api.APIClient({ origin, fetch: (...args): Promise<Response> => fetch(...args) });
 
 export async function api<E extends keyof Misskey.Endpoints, O extends Misskey.Endpoints[E]['req']>(endpoint: E, userId?: string, options?: O): Promise<void | ReturnType<typeof cli.request<E, O>>> {
-	let account: { token: string; id: string } | void;
+	let account: { token: string; id: string } | void = undefined;
 
 	if (userId) {
 		account = await getAccountFromId(userId);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2ce08699c0c00f06fae8131255a2c948a68507b7..5466d7b2088991c86e9460a1c66bd8b370c8ff9b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -25,8 +25,8 @@ importers:
         specifier: 8.4.31
         version: 8.4.31
       terser:
-        specifier: 5.20.0
-        version: 5.20.0
+        specifier: 5.21.0
+        version: 5.21.0
       typescript:
         specifier: 5.2.2
         version: 5.2.2
@@ -36,11 +36,11 @@ importers:
         version: 4.4.0
     devDependencies:
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.7.3
-        version: 6.7.3(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)(typescript@5.2.2)
       '@typescript-eslint/parser':
-        specifier: 6.7.3
-        version: 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
@@ -48,8 +48,8 @@ importers:
         specifier: 13.3.0
         version: 13.3.0
       eslint:
-        specifier: 8.50.0
-        version: 8.50.0
+        specifier: 8.51.0
+        version: 8.51.0
       start-server-and-test:
         specifier: 2.0.1
         version: 2.0.1
@@ -99,14 +99,14 @@ importers:
         specifier: 8.2.0
         version: 8.2.0
       '@nestjs/common':
-        specifier: 10.2.6
-        version: 10.2.6(reflect-metadata@0.1.13)(rxjs@7.8.1)
+        specifier: 10.2.7
+        version: 10.2.7(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nestjs/core':
-        specifier: 10.2.6
-        version: 10.2.6(@nestjs/common@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1)
+        specifier: 10.2.7
+        version: 10.2.7(@nestjs/common@10.2.7)(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nestjs/testing':
-        specifier: 10.2.6
-        version: 10.2.6(@nestjs/common@10.2.6)(@nestjs/core@10.2.6)
+        specifier: 10.2.7
+        version: 10.2.7(@nestjs/common@10.2.7)(@nestjs/core@10.2.7)
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
@@ -121,10 +121,10 @@ importers:
         version: 2.1.5
       '@swc/cli':
         specifier: 0.1.62
-        version: 0.1.62(@swc/core@1.3.90)(chokidar@3.5.3)
+        version: 0.1.62(@swc/core@1.3.92)(chokidar@3.5.3)
       '@swc/core':
-        specifier: 1.3.90
-        version: 1.3.90
+        specifier: 1.3.92
+        version: 1.3.92
       accepts:
         specifier: 1.3.8
         version: 1.3.8
@@ -147,8 +147,8 @@ importers:
         specifier: 1.20.2
         version: 1.20.2
       bullmq:
-        specifier: 4.11.4
-        version: 4.11.4
+        specifier: 4.12.3
+        version: 4.12.3
       cacheable-lookup:
         specifier: 7.0.0
         version: 7.0.0
@@ -261,8 +261,8 @@ importers:
         specifier: 3.3.2
         version: 3.3.2
       nodemailer:
-        specifier: 6.9.5
-        version: 6.9.5
+        specifier: 6.9.6
+        version: 6.9.6
       nsfwjs:
         specifier: 2.4.2
         version: 2.4.2(@tensorflow/tfjs@4.4.0)
@@ -279,8 +279,8 @@ importers:
         specifier: 0.0.14
         version: 0.0.14
       otpauth:
-        specifier: 9.1.4
-        version: 9.1.4
+        specifier: 9.1.5
+        version: 9.1.5
       parse5:
         specifier: 7.1.2
         version: 7.1.2
@@ -354,8 +354,8 @@ importers:
         specifier: github:misskey-dev/summaly
         version: github.com/misskey-dev/summaly/d2d8db49943ccb201c1b1b283e9d0a630519fac7
       systeminformation:
-        specifier: 5.21.9
-        version: 5.21.9
+        specifier: 5.21.11
+        version: 5.21.11
       tinycolor2:
         specifier: 1.6.0
         version: 1.6.0
@@ -489,7 +489,7 @@ importers:
         version: 8.0.0
       '@swc/jest':
         specifier: 0.2.29
-        version: 0.2.29(@swc/core@1.3.90)
+        version: 0.2.29(@swc/core@1.3.92)
       '@types/accepts':
         specifier: 1.3.5
         version: 1.3.5
@@ -539,8 +539,8 @@ importers:
         specifier: 0.7.32
         version: 0.7.32
       '@types/node':
-        specifier: 20.7.1
-        version: 20.7.1
+        specifier: 20.8.4
+        version: 20.8.4
       '@types/node-fetch':
         specifier: 3.0.3
         version: 3.0.3
@@ -557,8 +557,8 @@ importers:
         specifier: 0.1.0
         version: 0.1.0
       '@types/pg':
-        specifier: 8.10.3
-        version: 8.10.3
+        specifier: 8.10.4
+        version: 8.10.4
       '@types/pug':
         specifier: 2.0.7
         version: 2.0.7
@@ -608,11 +608,11 @@ importers:
         specifier: 8.5.6
         version: 8.5.6
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.7.3
-        version: 6.7.3(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)(typescript@5.2.2)
       '@typescript-eslint/parser':
-        specifier: 6.7.3
-        version: 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       aws-sdk-client-mock:
         specifier: 3.0.0
         version: 3.0.0
@@ -620,17 +620,17 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       eslint:
-        specifier: 8.50.0
-        version: 8.50.0
+        specifier: 8.51.0
+        version: 8.51.0
       eslint-plugin-import:
         specifier: 2.28.1
-        version: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)
+        version: 2.28.1(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)
       execa:
         specifier: 8.0.1
         version: 8.0.1
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.7.1)
+        version: 29.7.0(@types/node@20.8.4)
       jest-mock:
         specifier: 29.7.0
         version: 29.7.0
@@ -647,17 +647,17 @@ importers:
         specifier: 2.1.1
         version: 2.1.1
       '@rollup/plugin-alias':
-        specifier: 5.0.0
-        version: 5.0.0(rollup@3.29.4)
+        specifier: 5.0.1
+        version: 5.0.1(rollup@4.0.2)
       '@rollup/plugin-json':
-        specifier: 6.0.0
-        version: 6.0.0(rollup@3.29.4)
+        specifier: 6.0.1
+        version: 6.0.1(rollup@4.0.2)
       '@rollup/plugin-replace':
-        specifier: 5.0.2
-        version: 5.0.2(rollup@3.29.4)
+        specifier: 5.0.3
+        version: 5.0.3(rollup@4.0.2)
       '@rollup/pluginutils':
-        specifier: 5.0.4
-        version: 5.0.4(rollup@3.29.4)
+        specifier: 5.0.5
+        version: 5.0.5(rollup@4.0.2)
       '@syuilo/aiscript':
         specifier: 0.16.0
         version: 0.16.0
@@ -665,11 +665,11 @@ importers:
         specifier: 2.37.0
         version: 2.37.0
       '@vitejs/plugin-vue':
-        specifier: 4.3.4
-        version: 4.3.4(vite@4.4.9)(vue@3.3.4)
+        specifier: 4.4.0
+        version: 4.4.0(vite@4.4.11)(vue@3.3.4)
       '@vue-macros/reactivity-transform':
         specifier: 0.3.23
-        version: 0.3.23(rollup@3.29.4)(vue@3.3.4)
+        version: 0.3.23(rollup@4.0.2)(vue@3.3.4)
       '@vue/compiler-sfc':
         specifier: 3.3.4
         version: 3.3.4
@@ -707,8 +707,8 @@ importers:
         specifier: 2.0.1
         version: 2.0.1(chart.js@4.4.0)
       chromatic:
-        specifier: 7.2.0
-        version: 7.2.0
+        specifier: 7.2.3
+        version: 7.2.3
       compare-versions:
         specifier: 6.1.0
         version: 6.1.0
@@ -752,8 +752,8 @@ importers:
         specifier: workspace:*
         version: link:../misskey-js
       photoswipe:
-        specifier: 5.4.1
-        version: 5.4.1
+        specifier: 5.4.2
+        version: 5.4.2
       prismjs:
         specifier: 1.29.0
         version: 1.29.0
@@ -764,14 +764,14 @@ importers:
         specifier: 0.2.1
         version: 0.2.1
       rollup:
-        specifier: 3.29.4
-        version: 3.29.4
+        specifier: 4.0.2
+        version: 4.0.2
       sanitize-html:
         specifier: 2.11.0
         version: 2.11.0
       sass:
-        specifier: 1.68.0
-        version: 1.68.0
+        specifier: 1.69.1
+        version: 1.69.1
       strict-event-emitter-types:
         specifier: 2.0.0
         version: 2.0.0
@@ -809,8 +809,8 @@ importers:
         specifier: 1.8.1
         version: 1.8.1
       vite:
-        specifier: 4.4.9
-        version: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+        specifier: 4.4.11
+        version: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
       vue:
         specifier: 3.3.4
         version: 3.3.4
@@ -822,59 +822,59 @@ importers:
         version: 4.1.0(vue@3.3.4)
     devDependencies:
       '@storybook/addon-actions':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-essentials':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-interactions':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-links':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-storysource':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addons':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/blocks':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/core-events':
-        specifier: 7.4.5
-        version: 7.4.5
+        specifier: 7.4.6
+        version: 7.4.6
       '@storybook/jest':
-        specifier: 0.2.2
-        version: 0.2.2(vitest@0.34.5)
+        specifier: 0.2.3
+        version: 0.2.3(vitest@0.34.6)
       '@storybook/manager-api':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/preview-api':
-        specifier: 7.4.5
-        version: 7.4.5
+        specifier: 7.4.6
+        version: 7.4.6
       '@storybook/react':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
       '@storybook/react-vite':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.9)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)(rollup@4.0.2)(typescript@5.2.2)(vite@4.4.11)
       '@storybook/testing-library':
-        specifier: 0.2.1
-        version: 0.2.1
+        specifier: 0.2.2
+        version: 0.2.2
       '@storybook/theming':
-        specifier: 7.4.5
-        version: 7.4.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.4.6
+        version: 7.4.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/types':
-        specifier: 7.4.5
-        version: 7.4.5
+        specifier: 7.4.6
+        version: 7.4.6
       '@storybook/vue3':
-        specifier: 7.4.5
-        version: 7.4.5(@vue/compiler-core@3.3.4)(vue@3.3.4)
+        specifier: 7.4.6
+        version: 7.4.6(@vue/compiler-core@3.3.4)(vue@3.3.4)
       '@storybook/vue3-vite':
-        specifier: 7.4.5
-        version: 7.4.5(@vue/compiler-core@3.3.4)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(vite@4.4.9)(vue@3.3.4)
+        specifier: 7.4.6
+        version: 7.4.6(@vue/compiler-core@3.3.4)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(vite@4.4.11)(vue@3.3.4)
       '@testing-library/vue':
         specifier: 7.0.0
         version: 7.0.0(@vue/compiler-sfc@3.3.4)(vue@3.3.4)
@@ -891,8 +891,8 @@ importers:
         specifier: 4.0.3
         version: 4.0.3
       '@types/node':
-        specifier: 20.7.1
-        version: 20.7.1
+        specifier: 20.8.4
+        version: 20.8.4
       '@types/punycode':
         specifier: 2.1.0
         version: 2.1.0
@@ -906,8 +906,8 @@ importers:
         specifier: 1.4.4
         version: 1.4.4
       '@types/uuid':
-        specifier: 9.0.4
-        version: 9.0.4
+        specifier: 9.0.5
+        version: 9.0.5
       '@types/websocket':
         specifier: 1.0.7
         version: 1.0.7
@@ -915,14 +915,14 @@ importers:
         specifier: 8.5.6
         version: 8.5.6
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.7.3
-        version: 6.7.3(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)(typescript@5.2.2)
       '@typescript-eslint/parser':
-        specifier: 6.7.3
-        version: 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       '@vitest/coverage-v8':
-        specifier: 0.34.5
-        version: 0.34.5(vitest@0.34.5)
+        specifier: 0.34.6
+        version: 0.34.6(vitest@0.34.6)
       '@vue/runtime-core':
         specifier: 3.3.4
         version: 3.3.4
@@ -936,14 +936,14 @@ importers:
         specifier: 13.3.0
         version: 13.3.0
       eslint:
-        specifier: 8.50.0
-        version: 8.50.0
+        specifier: 8.51.0
+        version: 8.51.0
       eslint-plugin-import:
         specifier: 2.28.1
-        version: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)
+        version: 2.28.1(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)
       eslint-plugin-vue:
         specifier: 9.17.0
-        version: 9.17.0(eslint@8.50.0)
+        version: 9.17.0(eslint@8.51.0)
       fast-glob:
         specifier: 3.3.1
         version: 3.3.1
@@ -954,11 +954,11 @@ importers:
         specifier: 4.0.5
         version: 4.0.5
       msw:
-        specifier: 1.3.1
-        version: 1.3.1(typescript@5.2.2)
+        specifier: 1.3.2
+        version: 1.3.2(typescript@5.2.2)
       msw-storybook-addon:
         specifier: 1.8.0
-        version: 1.8.0(msw@1.3.1)
+        version: 1.8.0(msw@1.3.2)
       nodemon:
         specifier: 3.0.1
         version: 3.0.1
@@ -975,11 +975,11 @@ importers:
         specifier: 2.0.1
         version: 2.0.1
       storybook:
-        specifier: 7.4.5
-        version: 7.4.5
+        specifier: 7.4.6
+        version: 7.4.6
       storybook-addon-misskey-theme:
         specifier: github:misskey-dev/storybook-addon-misskey-theme
-        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.4.5)(@storybook/components@7.4.4)(@storybook/core-events@7.4.5)(@storybook/manager-api@7.4.5)(@storybook/preview-api@7.4.5)(@storybook/theming@7.4.5)(@storybook/types@7.4.5)(react-dom@18.2.0)(react@18.2.0)
+        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.4.6)(@storybook/components@7.4.6)(@storybook/core-events@7.4.6)(@storybook/manager-api@7.4.6)(@storybook/preview-api@7.4.6)(@storybook/theming@7.4.6)(@storybook/types@7.4.6)(react-dom@18.2.0)(react@18.2.0)
       summaly:
         specifier: github:misskey-dev/summaly
         version: github.com/misskey-dev/summaly/d2d8db49943ccb201c1b1b283e9d0a630519fac7
@@ -987,26 +987,26 @@ importers:
         specifier: 1.0.3
         version: 1.0.3
       vitest:
-        specifier: 0.34.5
-        version: 0.34.5(happy-dom@10.0.3)(sass@1.68.0)(terser@5.20.0)
+        specifier: 0.34.6
+        version: 0.34.6(happy-dom@10.0.3)(sass@1.69.1)(terser@5.21.0)
       vitest-fetch-mock:
         specifier: 0.2.2
-        version: 0.2.2(vitest@0.34.5)
+        version: 0.2.2(vitest@0.34.6)
       vue-eslint-parser:
-        specifier: 9.3.1
-        version: 9.3.1(eslint@8.50.0)
+        specifier: 9.3.2
+        version: 9.3.2(eslint@8.51.0)
       vue-tsc:
-        specifier: 1.8.15
-        version: 1.8.15(typescript@5.2.2)
+        specifier: 1.8.18
+        version: 1.8.18(typescript@5.2.2)
 
   packages/misskey-js:
     dependencies:
       '@swc/cli':
         specifier: 0.1.62
-        version: 0.1.62(@swc/core@1.3.90)(chokidar@3.5.3)
+        version: 0.1.62(@swc/core@1.3.92)(chokidar@3.5.3)
       '@swc/core':
-        specifier: 1.3.90
-        version: 1.3.90
+        specifier: 1.3.92
+        version: 1.3.92
       eventemitter3:
         specifier: 5.0.1
         version: 5.0.1
@@ -1015,29 +1015,29 @@ importers:
         version: 4.4.0
     devDependencies:
       '@microsoft/api-extractor':
-        specifier: 7.37.2
-        version: 7.37.2(@types/node@20.7.1)
+        specifier: 7.38.0
+        version: 7.38.0(@types/node@20.8.4)
       '@swc/jest':
         specifier: 0.2.29
-        version: 0.2.29(@swc/core@1.3.90)
+        version: 0.2.29(@swc/core@1.3.92)
       '@types/jest':
         specifier: 29.5.5
         version: 29.5.5
       '@types/node':
-        specifier: 20.7.1
-        version: 20.7.1
+        specifier: 20.8.4
+        version: 20.8.4
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.7.3
-        version: 6.7.3(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)(typescript@5.2.2)
       '@typescript-eslint/parser':
-        specifier: 6.7.3
-        version: 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       eslint:
-        specifier: 8.50.0
-        version: 8.50.0
+        specifier: 8.51.0
+        version: 8.51.0
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.7.1)
+        version: 29.7.0(@types/node@20.8.4)
       jest-fetch-mock:
         specifier: 3.0.3
         version: 3.0.3
@@ -1067,17 +1067,17 @@ importers:
         version: link:../misskey-js
     devDependencies:
       '@typescript-eslint/parser':
-        specifier: 6.7.3
-        version: 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+        specifier: 6.7.5
+        version: 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       '@typescript/lib-webworker':
         specifier: npm:@types/serviceworker@0.0.67
         version: /@types/serviceworker@0.0.67
       eslint:
-        specifier: 8.50.0
-        version: 8.50.0
+        specifier: 8.51.0
+        version: 8.51.0
       eslint-plugin-import:
         specifier: 2.28.1
-        version: 2.28.1(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)
+        version: 2.28.1(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)
       typescript:
         specifier: 5.2.2
         version: 5.2.2
@@ -3686,13 +3686,13 @@ packages:
     dev: false
     optional: true
 
-  /@eslint-community/eslint-utils@4.4.0(eslint@8.50.0):
+  /@eslint-community/eslint-utils@4.4.0(eslint@8.51.0):
     resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
     dependencies:
-      eslint: 8.50.0
+      eslint: 8.51.0
       eslint-visitor-keys: 3.4.3
     dev: true
 
@@ -3718,8 +3718,8 @@ packages:
       - supports-color
     dev: true
 
-  /@eslint/js@8.50.0:
-    resolution: {integrity: sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==}
+  /@eslint/js@8.51.0:
+    resolution: {integrity: sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
@@ -4008,7 +4008,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       chalk: 4.1.2
       jest-message-util: 29.7.0
       jest-util: 29.7.0
@@ -4029,14 +4029,14 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.7.1
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.7.1)
+      jest-config: 29.7.0(@types/node@20.8.4)
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -4071,7 +4071,7 @@ packages:
     dependencies:
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       jest-mock: 29.7.0
     dev: true
 
@@ -4098,7 +4098,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@sinonjs/fake-timers': 10.3.0
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       jest-message-util: 29.7.0
       jest-mock: 29.7.0
       jest-util: 29.7.0
@@ -4131,7 +4131,7 @@ packages:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.18
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       chalk: 4.1.2
       collect-v8-coverage: 1.0.1
       exit: 0.1.2
@@ -4225,7 +4225,7 @@ packages:
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -4237,12 +4237,12 @@ packages:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       '@types/yargs': 17.0.19
       chalk: 4.1.2
     dev: true
 
-  /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.2.2)(vite@4.4.9):
+  /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.2.2)(vite@4.4.11):
     resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==}
     peerDependencies:
       typescript: '>= 4.3.x'
@@ -4256,7 +4256,7 @@ packages:
       magic-string: 0.27.0
       react-docgen-typescript: 2.2.2(typescript@5.2.2)
       typescript: 5.2.2
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
     dev: true
 
   /@jridgewell/gen-mapping@0.3.2:
@@ -4341,24 +4341,24 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@microsoft/api-extractor-model@7.28.2(@types/node@20.7.1):
+  /@microsoft/api-extractor-model@7.28.2(@types/node@20.8.4):
     resolution: {integrity: sha512-vkojrM2fo3q4n4oPh4uUZdjJ2DxQ2+RnDQL/xhTWSRUNPF6P4QyrvY357HBxbnltKcYu+nNNolVqc6TIGQ73Ig==}
     dependencies:
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.61.0(@types/node@20.7.1)
+      '@rushstack/node-core-library': 3.61.0(@types/node@20.8.4)
     transitivePeerDependencies:
       - '@types/node'
     dev: true
 
-  /@microsoft/api-extractor@7.37.2(@types/node@20.7.1):
-    resolution: {integrity: sha512-b4tr1rTto9/utTjbuqRwfQP2mzP0ACCmJMUY0JIOfOQ3tewGOkMCIRpIS5kcv5/nURekoAY06hNwHmkVsv/s1g==}
+  /@microsoft/api-extractor@7.38.0(@types/node@20.8.4):
+    resolution: {integrity: sha512-e1LhZYnfw+JEebuY2bzhw0imDCl1nwjSThTrQqBXl40hrVo6xm3j/1EpUr89QyzgjqmAwek2ZkIVZbrhaR+cqg==}
     hasBin: true
     dependencies:
-      '@microsoft/api-extractor-model': 7.28.2(@types/node@20.7.1)
+      '@microsoft/api-extractor-model': 7.28.2(@types/node@20.8.4)
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.61.0(@types/node@20.7.1)
+      '@rushstack/node-core-library': 3.61.0(@types/node@20.8.4)
       '@rushstack/rig-package': 0.5.1
       '@rushstack/ts-command-line': 4.16.1
       colors: 1.2.5
@@ -4478,8 +4478,8 @@ packages:
       tar-fs: 2.1.1
     dev: true
 
-  /@nestjs/common@10.2.6(reflect-metadata@0.1.13)(rxjs@7.8.1):
-    resolution: {integrity: sha512-ma8R7n+FXsWM4XF9QXjjrsRceyRzid/xKmNKVOa/sTJntkVG8lL71BHBEfjtFvO6EJUqjs/15LbDc0iaN5nCwA==}
+  /@nestjs/common@10.2.7(reflect-metadata@0.1.13)(rxjs@7.8.1):
+    resolution: {integrity: sha512-cUtCRXiUstDmh4bSBhVbq4cI439Gngp4LgLGLBmd5dqFQodfXKnSD441ldYfFiLz4rbUsnoMJz/8ZjuIEI+B7A==}
     peerDependencies:
       class-transformer: '*'
       class-validator: '*'
@@ -4498,8 +4498,8 @@ packages:
       uid: 2.0.2
     dev: false
 
-  /@nestjs/core@10.2.6(@nestjs/common@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1):
-    resolution: {integrity: sha512-oGQ2CoBeFRT7egG47MFqS89xlXBTIRZBkRpKRTPMftEfL1RMXhXIcIIaGfzp11wx6qxrBVxBXpVLM09oaqHpaQ==}
+  /@nestjs/core@10.2.7(@nestjs/common@10.2.7)(reflect-metadata@0.1.13)(rxjs@7.8.1):
+    resolution: {integrity: sha512-5GSu53QUUcwX17sNmlJPa1I0wIeAZOKbedyVuQx0ZAwWVa9g0wJBbsNP+R4EJ+j5Dkdzt/8xkiZvnKt8RFRR8g==}
     requiresBuild: true
     peerDependencies:
       '@nestjs/common': ^10.0.0
@@ -4516,7 +4516,7 @@ packages:
       '@nestjs/websockets':
         optional: true
     dependencies:
-      '@nestjs/common': 10.2.6(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@nestjs/common': 10.2.7(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nuxtjs/opencollective': 0.3.2
       fast-safe-stringify: 2.1.1
       iterare: 1.2.1
@@ -4529,8 +4529,8 @@ packages:
       - encoding
     dev: false
 
-  /@nestjs/testing@10.2.6(@nestjs/common@10.2.6)(@nestjs/core@10.2.6):
-    resolution: {integrity: sha512-uxlxHhpSvG4yDTPmuPneoQL1/UnBkOkzE+Zaz6bwURg7lc3uS4ZsXl75OL3pYaJH37rHYXYT9bGcYSpxVbwIrg==}
+  /@nestjs/testing@10.2.7(@nestjs/common@10.2.7)(@nestjs/core@10.2.7):
+    resolution: {integrity: sha512-d2SIqiJIf/7NSILeNNWSdRvTTpHSouGgisGHwf5PVDC7z4/yXZw/wPO9eJhegnxFlqk6n2LW4QBTmMzbqjAfHA==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/core': ^10.0.0
@@ -4542,8 +4542,8 @@ packages:
       '@nestjs/platform-express':
         optional: true
     dependencies:
-      '@nestjs/common': 10.2.6(reflect-metadata@0.1.13)(rxjs@7.8.1)
-      '@nestjs/core': 10.2.6(@nestjs/common@10.2.6)(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@nestjs/common': 10.2.7(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@nestjs/core': 10.2.7(@nestjs/common@10.2.7)(reflect-metadata@0.1.13)(rxjs@7.8.1)
       tslib: 2.6.2
     dev: false
 
@@ -5189,51 +5189,51 @@ packages:
       '@babel/runtime': 7.22.10
     dev: true
 
-  /@rollup/plugin-alias@5.0.0(rollup@3.29.4):
-    resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==}
+  /@rollup/plugin-alias@5.0.1(rollup@4.0.2):
+    resolution: {integrity: sha512-JObvbWdOHoMy9W7SU0lvGhDtWq9PllP5mjpAy+TUslZG/WzOId9u80Hsqq1vCUn9pFJ0cxpdcnAv+QzU2zFH3Q==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
     peerDependenciesMeta:
       rollup:
         optional: true
     dependencies:
-      rollup: 3.29.4
+      rollup: 4.0.2
       slash: 4.0.0
     dev: false
 
-  /@rollup/plugin-json@6.0.0(rollup@3.29.4):
-    resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==}
+  /@rollup/plugin-json@6.0.1(rollup@4.0.2):
+    resolution: {integrity: sha512-RgVfl5hWMkxN1h/uZj8FVESvPuBJ/uf6ly6GTj0GONnkfoBN5KC0MSz+PN2OLDgYXMhtG0mWpTrkiOjoxAIevw==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
     peerDependenciesMeta:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.4(rollup@3.29.4)
-      rollup: 3.29.4
+      '@rollup/pluginutils': 5.0.5(rollup@4.0.2)
+      rollup: 4.0.2
     dev: false
 
-  /@rollup/plugin-replace@5.0.2(rollup@3.29.4):
-    resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==}
+  /@rollup/plugin-replace@5.0.3(rollup@4.0.2):
+    resolution: {integrity: sha512-je7fu05B800IrMlWjb2wzJcdXzHYW46iTipfChnBDbIbDXhASZs27W1B58T2Yf45jZtJUONegpbce+9Ut2Ti/Q==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
     peerDependenciesMeta:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.4(rollup@3.29.4)
+      '@rollup/pluginutils': 5.0.5(rollup@4.0.2)
       magic-string: 0.27.0
-      rollup: 3.29.4
+      rollup: 4.0.2
     dev: false
 
-  /@rollup/pluginutils@5.0.4(rollup@3.29.4):
-    resolution: {integrity: sha512-0KJnIoRI8A+a1dqOYLxH8vBf8bphDmty5QvIm2hqm7oFCFYKCAZWWd2hXgMibaPsNDhI0AtpYfQZJG47pt/k4g==}
+  /@rollup/pluginutils@5.0.5(rollup@4.0.2):
+    resolution: {integrity: sha512-6aEYR910NyP73oHiJglti74iRyOwgFU4x3meH/H8OJx6Ry0j6cOVZ5X/wTvub7G7Ao6qaHBEaNsV3GLJkSsF+Q==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
-      rollup: ^1.20.0||^2.0.0||^3.0.0
+      rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
     peerDependenciesMeta:
       rollup:
         optional: true
@@ -5241,9 +5241,93 @@ packages:
       '@types/estree': 1.0.2
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 3.29.4
+      rollup: 4.0.2
+
+  /@rollup/rollup-android-arm-eabi@4.0.2:
+    resolution: {integrity: sha512-xDvk1pT4vaPU2BOLy0MqHMdYZyntqpaBf8RhBiezlqG9OjY8F50TyctHo8znigYKd+QCFhCmlmXHOL/LoaOl3w==}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    optional: true
 
-  /@rushstack/node-core-library@3.61.0(@types/node@20.7.1):
+  /@rollup/rollup-android-arm64@4.0.2:
+    resolution: {integrity: sha512-lqCglytY3E6raze27DD9VQJWohbwCxzqs9aSHcj5X/8hJpzZfNdbsr4Ja9Hqp6iPyF53+5PtPx0pKRlkSvlHZg==}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-darwin-arm64@4.0.2:
+    resolution: {integrity: sha512-nkBKItS6E6CCzvRwgiKad+j+1ibmL7SIInj7oqMWmdkCjiSX6VeVZw2mLlRKIUL+JjsBgpATTfo7BiAXc1v0jA==}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-darwin-x64@4.0.2:
+    resolution: {integrity: sha512-vX2C8xvWPIbpEgQht95+dY6BReKAvtDgPDGi0XN0kWJKkm4WdNmq5dnwscv/zxvi+n6jUTBhs6GtpkkWT4q8Gg==}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-linux-arm-gnueabihf@4.0.2:
+    resolution: {integrity: sha512-DVFIfcHOjgmeHOAqji4xNz2wczt1Bmzy9MwBZKBa83SjBVO/i38VHDR+9ixo8QpBOiEagmNw12DucG+v55tCrg==}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-linux-arm64-gnu@4.0.2:
+    resolution: {integrity: sha512-GCK/a9ItUxPI0V5hQEJjH4JtOJO90GF2Hja7TO+EZ8rmkGvEi8/ZDMhXmcuDpQT7/PWrTT9RvnG8snMd5SrhBQ==}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-linux-arm64-musl@4.0.2:
+    resolution: {integrity: sha512-cLuBp7rOjIB1R2j/VazjCmHC7liWUur2e9mFflLJBAWCkrZ+X0+QwHLvOQakIwDymungzAKv6W9kHZnTp/Mqrg==}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-linux-x64-gnu@4.0.2:
+    resolution: {integrity: sha512-Zqw4iVnJr2naoyQus0yLy7sLtisCQcpdMKUCeXPBjkJtpiflRime/TMojbnl8O3oxUAj92mxr+t7im/RbgA20w==}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-linux-x64-musl@4.0.2:
+    resolution: {integrity: sha512-jJRU9TyUD/iMqjf8aLAp7XiN3pIj5v6Qcu+cdzBfVTKDD0Fvua4oUoK8eVJ9ZuKBEQKt3WdlcwJXFkpmMLk6kg==}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-win32-arm64-msvc@4.0.2:
+    resolution: {integrity: sha512-ZkS2NixCxHKC4zbOnw64ztEGGDVIYP6nKkGBfOAxEPW71Sji9v8z3yaHNuae/JHPwXA+14oDefnOuVfxl59SmQ==}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-win32-ia32-msvc@4.0.2:
+    resolution: {integrity: sha512-3SKjj+tvnZ0oZq2BKB+fI+DqYI83VrRzk7eed8tJkxeZ4zxJZcLSE8YDQLYGq1tZAnAX+H076RHHB4gTZXsQzw==}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
+  /@rollup/rollup-win32-x64-msvc@4.0.2:
+    resolution: {integrity: sha512-MBdJIOxRauKkry7t2q+rTHa3aWjVez2eioWg+etRVS3dE4tChhmt5oqZYr48R6bPmcwEhxQr96gVRfeQrLbqng==}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
+  /@rushstack/node-core-library@3.61.0(@types/node@20.8.4):
     resolution: {integrity: sha512-tdOjdErme+/YOu4gPed3sFS72GhtWCgNV9oDsHDnoLY5oDfwjKUc9Z+JOZZ37uAxcm/OCahDHfuu2ugqrfWAVQ==}
     peerDependencies:
       '@types/node': '*'
@@ -5251,7 +5335,7 @@ packages:
       '@types/node':
         optional: true
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       colors: 1.2.5
       fs-extra: 7.0.1
       import-lazy: 4.0.0
@@ -5806,8 +5890,8 @@ packages:
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
     dev: false
 
-  /@storybook/addon-actions@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-FkjJWmPN/+duLSkRwfa2bwlwjKfY6yCXYn7CRzn3rb64B8f50NB79zAgVLHjkJh9l6T3DIlWtol6vqPHj1aRpw==}
+  /@storybook/addon-actions@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-SsqZr3js5NinKPnC8AeNI7Ij+Q6fIl9tRdRmSulEgjksjOg7E5S1/Wsn5Bb2CCgj7MaX6VxGyC7s3XskQtDiIQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5817,14 +5901,14 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       dequal: 2.0.3
       lodash: 4.17.21
       polished: 4.2.2
@@ -5840,8 +5924,8 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addon-backgrounds@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-fTq9E1WrYH/9hwDemFVLVcaI2iSSuwWnvY/8tqGrY9xhQF5dIpeHf+z8+HWXpau7e6Z0/WiYR+1vwAcIKt95LQ==}
+  /@storybook/addon-backgrounds@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-+LHTZB/ZYMAzkyD5ZxSriBsqmsrvIaW/Nnd/BeuXGbkrVKKqM0qAKiFZAfjc2WchA1piVNy0/1Rsf+kuYCEiJw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5851,14 +5935,14 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5868,8 +5952,8 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addon-controls@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Mxs56jt44HIbZ4gJa0AII1U8GqEGFsvcM5Iob0ETNpxCW5Kj5iHly/4Ws0RFWPH/krrQKaLpWXaUxKmbtEzhJA==}
+  /@storybook/addon-controls@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-4lq3sycEUIsK8SUWDYc60QgF4vV9FZZ3lDr6M7j2W9bOnvGw49d2fbdlnq+bX1ZprZZ9VgglQpBAorQB3BXZRw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5879,16 +5963,16 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.4.5
-      '@storybook/core-events': 7.4.5
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.4.5
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/blocks': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.4.6
+      '@storybook/core-events': 7.4.6
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 7.4.6
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       lodash: 4.17.21
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5900,27 +5984,27 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-docs@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-KjFVeq8oL7ZC1gsk8iY3Nn0RrHHUpczmOTCd8FeVNmKD4vq+dkPb/8bJLy+jArmIZ8vRhknpTh6kp1BqB7qHGQ==}
+  /@storybook/addon-docs@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-dLaub+XWFq4hChw+xfuF9yYg0Txp77FUawKoAigccfjWXx+OOhRV3XTuAcknpXkYq94GWynHgUFXosXT9kbDNA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
       '@jest/transform': 29.7.0
       '@mdx-js/react': 2.3.0(react@18.2.0)
-      '@storybook/blocks': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/csf-plugin': 7.4.5
-      '@storybook/csf-tools': 7.4.5
+      '@storybook/blocks': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/csf-plugin': 7.4.6
+      '@storybook/csf-tools': 7.4.6
       '@storybook/global': 5.0.0
       '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.4.5
-      '@storybook/postinstall': 7.4.5
-      '@storybook/preview-api': 7.4.5
-      '@storybook/react-dom-shim': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/node-logger': 7.4.6
+      '@storybook/postinstall': 7.4.6
+      '@storybook/preview-api': 7.4.6
+      '@storybook/react-dom-shim': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       fs-extra: 11.1.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5934,25 +6018,25 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-essentials@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-H7zZWJXZP0UU2kXfo9zlQfjIKHuuqYBK7PZ2/SL5y08mTrbtt1BfqYScz3xRvHocaFcsBWCXdy8jJULT4KFUpw==}
+  /@storybook/addon-essentials@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-dWodufrt71TK7ELkeIvVae/x4PzECUlbOm57Iqqt4yQCyR291CgvI4PjeB8un2HbpcXCGZ+N/Oj3YkytvzBi4A==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/addon-actions': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-backgrounds': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-controls': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-docs': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-highlight': 7.4.5
-      '@storybook/addon-measure': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-outline': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-toolbars': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-viewport': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.4.5
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.4.5
-      '@storybook/preview-api': 7.4.5
+      '@storybook/addon-actions': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-backgrounds': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-controls': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-docs': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-highlight': 7.4.6
+      '@storybook/addon-measure': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-outline': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-toolbars': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-viewport': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.4.6
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 7.4.6
+      '@storybook/preview-api': 7.4.6
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
@@ -5963,16 +6047,16 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-highlight@7.4.5:
-    resolution: {integrity: sha512-6Ru411+Iis4m2weKb8kB1eEssLvCHwFqAf4fjcOC//O5Vaf5+beHYZJUm/rzD0k/oUHfLCBwDBSBY5TLRegkdA==}
+  /@storybook/addon-highlight@7.4.6:
+    resolution: {integrity: sha512-zCufxxD2KS5VwczxfkcBxe1oR/juTTn2H1Qm8kYvWCJQx3UxzX0+G9cwafbpV7eivqaufLweEwROkH+0KjAtkQ==}
     dependencies:
-      '@storybook/core-events': 7.4.5
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.4.5
+      '@storybook/preview-api': 7.4.6
     dev: true
 
-  /@storybook/addon-interactions@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-KDdV/THxj38VsuOevrUefev0rZPhzqUXCgrw1Jc2PsJGidHf9d9nnB7wbA9ZFYsxTz90M/Vk5sm7i1QkMmsquA==}
+  /@storybook/addon-interactions@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-zVZYrEPZPhNrXBuPqM7HbQvr6jwsje1sbCYj3wnp83U5wjciuqrngqHIlaSZ30zOWSfRVyzbyqL+JQZKA58BNA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5982,16 +6066,16 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/instrumenter': 7.4.5
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/instrumenter': 7.4.6
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       jest-mock: 27.5.1
       polished: 4.2.2
       react: 18.2.0
@@ -6004,8 +6088,8 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-links@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-eKczq3U5KfPLaxMUzzVQQrGVtzDshUmrSEEuWKf9ZbK3mh5yVuagIBb88edgUX58vZ3TJMvqQzq1+BtUoPHQ6Q==}
+  /@storybook/addon-links@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-BPygElZKX+CPI9Se6GJNk1dYc5oxuhA+vHigO1tBqhiM6VkHyFP3cvezJNQvpNYhkUnu3cxnZXb3UJnlRbPY3g==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6015,22 +6099,22 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/router': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/router': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       prop-types: 15.8.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-measure@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-FQGZniTH67nC1YPR4ep0p+isgxwLaNAmIAyCZWXPRTkZssIrnXVwNgi0A2QkHdxZvxj8yXGFTOVXLWEPT9YvFQ==}
+  /@storybook/addon-measure@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-nCymMLaHnxv8TE3yEM1A9Tulb1NuRXRNmtsdHTkjv7P1aWCxZo8A/GZaottKe/GLT8jSRjZ+dnpYWrbAhw6wTQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6040,13 +6124,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/types': 7.4.6
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       tiny-invariant: 1.3.1
@@ -6055,8 +6139,8 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addon-outline@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-eOH9BZzpehUz5FXD98OLnWgzmBFMvEB2kFfw5JiO7IRx7Fan80fx/WDQuMSNDOgLBCTTvsZ4TBMMXZHpw91WAw==}
+  /@storybook/addon-outline@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-errNUblRVDLpuEaHQPr/nsrnsUkD2ARmXawkRvizgDWLIDMDJYjTON3MUCaVx3x+hlZ3I6X//G5TVcma8tCc8A==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6066,13 +6150,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/types': 7.4.6
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
@@ -6081,8 +6165,8 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addon-storysource@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-aWQkW4IzDHRXdUyHPfksSdk4zK4gIJvXpxVCqX+oz3FuadmwZmhK1vWxNdm4Jo/0EZdwe2YZOBJwXHIwpZtigg==}
+  /@storybook/addon-storysource@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-qkfwvh/pgVBReuWqO25WyaD7jd6LVqhoIJ6rBWnmx+NBpTds+h3Yt3UJCHgvweIrfSF8J3IqzaTxmmNjnkcrRw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6092,13 +6176,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/router': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/source-loader': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/router': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/source-loader': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
       estraverse: 5.3.0
       prop-types: 15.8.1
       react: 18.2.0
@@ -6110,8 +6194,8 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addon-toolbars@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-PZlwUTIdQ18de3zNb+627VSF4UrCGIXDdikyO9O5j2Cd0xfr5uhS6tgQ+3AT0DfUj0UIkKxilwcAt+agpNyicA==}
+  /@storybook/addon-toolbars@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-L9m2FBcKeteGq7qIYsMJr0LEfiH7Wdrv5IDcldZTn68eZUJTh1p4GdJZcOmzX1P5IFRr76hpu03iWsNlWQjpbQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6121,11 +6205,11 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     transitivePeerDependencies:
@@ -6133,8 +6217,8 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addon-viewport@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-SBLnUMIztVrqJ0fRCsVg9KZ29APLIxqAvTsYHF3twy5KB2naeCFuX3K9LxSH7vbROI6zHEfnPduz/Ykyvu9yUg==}
+  /@storybook/addon-viewport@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-INDtk54j7bi7NgxMfd2ATmbA0J7nAd6X8itMkLIyPuPJtx8bYHPDORyemDOd0AojgmAdTOAyUtDYdI/PFeo4Cw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6144,13 +6228,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
       memoizerific: 1.11.3
       prop-types: 15.8.1
       react: 18.2.0
@@ -6160,36 +6244,36 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/addons@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-jmdQf39XhwVi8d0J99qpk51fOAwNhYlCtVctvFWPX4qC1cq1d1pxLmTb5OBV2VHQ11BKwlKLzA7coiOgAQmNRg==}
+  /@storybook/addons@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-c+4awrtwNlJayFdgLkEXa5H2Gj+KNlxuN+Z5oDAdZBLqXI8g0gn7eYO2F/eCSIDWdd/+zcU2uq57XPFKc8veHQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/types': 7.4.6
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/blocks@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-FhAIkCT2HrzJcKsC3mL5+uG3GrbS23mYAT1h3iyPjCliZzxfCCI9UCMUXqYx4Z/FmAGJgpsQQXiBFZuoTHO9aQ==}
+  /@storybook/blocks@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-HxBSAeOiTZW2jbHQlo1upRWFgoMsaAyKijUFf5MwwMNIesXCuuTGZDJ3xTABwAVLK2qC9Ektfbo0CZCiPVuDRQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/channels': 7.4.5
-      '@storybook/client-logger': 7.4.5
-      '@storybook/components': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
+      '@storybook/channels': 7.4.6
+      '@storybook/client-logger': 7.4.6
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
       '@storybook/csf': 0.1.0
-      '@storybook/docs-tools': 7.4.5
+      '@storybook/docs-tools': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       '@types/lodash': 4.14.191
       color-convert: 2.0.1
       dequal: 2.0.3
@@ -6211,13 +6295,13 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-manager@7.4.5:
-    resolution: {integrity: sha512-Jhql8iZgK9cxDmG9NSTejsj5FptHni2TBa5Sea2Uz1NIBQ0OpzNdUfYVX6TN/PEq3QrWXTrAEKPqsL2qGjOrxw==}
+  /@storybook/builder-manager@7.4.6:
+    resolution: {integrity: sha512-zylZCD2rmyLOOFBFmUgtJg6UNUKmRNgXiig1XApzS2TkIbTZP827DsVEUl0ey/lskCe0uArkrEBR6ICba8p/Rw==}
     dependencies:
       '@fal-works/esbuild-plugin-global-externals': 2.1.2
-      '@storybook/core-common': 7.4.5
-      '@storybook/manager': 7.4.5
-      '@storybook/node-logger': 7.4.5
+      '@storybook/core-common': 7.4.6
+      '@storybook/manager': 7.4.6
+      '@storybook/node-logger': 7.4.6
       '@types/ejs': 3.1.2
       '@types/find-cache-dir': 3.2.1
       '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.18.17)
@@ -6235,8 +6319,8 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-vite@7.4.5(typescript@5.2.2)(vite@4.4.9):
-    resolution: {integrity: sha512-0aIMvBIx2U/DhDjdjWCW/KIG3HAJpus8NIUIvkVAUCaA7Vn8XvnSsdaRSTTxaaJReFZcIxDf7MebHSCJ0UEKqQ==}
+  /@storybook/builder-vite@7.4.6(typescript@5.2.2)(vite@4.4.11):
+    resolution: {integrity: sha512-xV9STYK+TkqWWTf2ydm6jx+7P70fjD2UPd1XTUw08uKszIjhuuxk+bG/OF5R1E25mPunAKXm6kBFh351AKejBg==}
     peerDependencies:
       '@preact/preset-vite': '*'
       typescript: '>= 4.3.x'
@@ -6250,15 +6334,15 @@ packages:
       vite-plugin-glimmerx:
         optional: true
     dependencies:
-      '@storybook/channels': 7.4.5
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-common': 7.4.5
-      '@storybook/csf-plugin': 7.4.5
+      '@storybook/channels': 7.4.6
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-common': 7.4.6
+      '@storybook/csf-plugin': 7.4.6
       '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.4.5
-      '@storybook/preview': 7.4.5
-      '@storybook/preview-api': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/node-logger': 7.4.6
+      '@storybook/preview': 7.4.6
+      '@storybook/preview-api': 7.4.6
+      '@storybook/types': 7.4.6
       '@types/find-cache-dir': 3.2.1
       browser-assert: 1.2.1
       es-module-lexer: 0.9.3
@@ -6270,50 +6354,39 @@ packages:
       remark-slug: 6.1.0
       rollup: 3.29.4
       typescript: 5.2.2
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  /@storybook/channels@7.4.4:
-    resolution: {integrity: sha512-YA2T3hClL95nFBBelm8wMOyWFDzfxKvyHAPQi+8YeYpZcPivwg/P9YnRhTTMbiZNkfoWKq4ZRuc79UP1iNLi3g==}
-    dependencies:
-      '@storybook/client-logger': 7.4.4
-      '@storybook/core-events': 7.4.4
-      '@storybook/global': 5.0.0
-      qs: 6.11.1
-      telejson: 7.2.0
-      tiny-invariant: 1.3.1
-    dev: true
-
-  /@storybook/channels@7.4.5:
-    resolution: {integrity: sha512-zWPZn4CxPFXsrrSRQ9JD8GmTeWeFYgr3sTBpe23hnhYookCXVNJ6AcaXogrT9b2ALfbB6MiFDbZIHHTgIgbWpg==}
+  /@storybook/channels@7.4.6:
+    resolution: {integrity: sha512-yPv/sfo2c18fM3fvG0i1xse63vG8l33Al/OU0k/dtovltPu001/HVa1QgBgsb/QrEfZtvGjGhmtdVeYb39fv3A==}
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
       qs: 6.11.1
       telejson: 7.2.0
       tiny-invariant: 1.3.1
     dev: true
 
-  /@storybook/cli@7.4.5:
-    resolution: {integrity: sha512-PlTkcHdKCugg3pD1zkBP/oFazcZsr7F3wdEmTvygfH0Cx/sQWg5wXBZCYKmf0ONRK4RKL3LVM8DRpeYiQVEFWg==}
+  /@storybook/cli@7.4.6:
+    resolution: {integrity: sha512-rRwaH8pOL+FHz/pJMEkNpMH2xvZvWsrl7obBYw26NQiHmiVSAkfHJicndSN1mwc+p5w+9iXthrgzbLtSAOSvkA==}
     hasBin: true
     dependencies:
       '@babel/core': 7.22.11
       '@babel/preset-env': 7.22.9(@babel/core@7.22.11)
       '@babel/types': 7.22.17
       '@ndelangen/get-tarball': 3.0.7
-      '@storybook/codemod': 7.4.5
-      '@storybook/core-common': 7.4.5
-      '@storybook/core-events': 7.4.5
-      '@storybook/core-server': 7.4.5
-      '@storybook/csf-tools': 7.4.5
-      '@storybook/node-logger': 7.4.5
-      '@storybook/telemetry': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/codemod': 7.4.6
+      '@storybook/core-common': 7.4.6
+      '@storybook/core-events': 7.4.6
+      '@storybook/core-server': 7.4.6
+      '@storybook/csf-tools': 7.4.6
+      '@storybook/node-logger': 7.4.6
+      '@storybook/telemetry': 7.4.6
+      '@storybook/types': 7.4.6
       '@types/semver': 7.5.3
       '@yarnpkg/fslib': 2.10.3
       '@yarnpkg/libzip': 2.3.0
@@ -6350,28 +6423,22 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@storybook/client-logger@7.4.4:
-    resolution: {integrity: sha512-rC/GcCy3DLtTI+oOHLBc6rq/c3oGF/mvdeWrhMM+berQplHJrOCI2pcldjVw8Fc25gLPK0LUlaOp1dfgt2Ri3Q==}
-    dependencies:
-      '@storybook/global': 5.0.0
-    dev: true
-
-  /@storybook/client-logger@7.4.5:
-    resolution: {integrity: sha512-Bn6eTAjhPDUfLpvuxhKkpDpOtkadfkSmkBNBZRu3r0Dzk2J1nNyKV5K6D8dOU4PFVof4z/gXYj5bktT29jKsmw==}
+  /@storybook/client-logger@7.4.6:
+    resolution: {integrity: sha512-XDw31ZziU//86PKuMRnmc+L/G0VopaGKENQOGEpvAXCU9IZASwGKlKAtcyosjrpi+ZiUXlMgUXCpXM7x3b1Ehw==}
     dependencies:
       '@storybook/global': 5.0.0
     dev: true
 
-  /@storybook/codemod@7.4.5:
-    resolution: {integrity: sha512-gyI2xliSv4vvnfNQN+0e3tRmT7beiq8q8iGjcBtpOhA2xrStyCR7PjbOfLXtRx2I/b50MDZMRTcckzeM3BLoWQ==}
+  /@storybook/codemod@7.4.6:
+    resolution: {integrity: sha512-lxmwEpwksCaAq96APN2YlooSDfKjJ1vKzN5Ni2EqQzf2TEXl7XQjLacHd7OOaII1kfsy+D5gNG4N5wBo7Ub30g==}
     dependencies:
       '@babel/core': 7.22.11
       '@babel/preset-env': 7.22.9(@babel/core@7.22.11)
       '@babel/types': 7.22.17
       '@storybook/csf': 0.1.0
-      '@storybook/csf-tools': 7.4.5
-      '@storybook/node-logger': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/csf-tools': 7.4.6
+      '@storybook/node-logger': 7.4.6
+      '@storybook/types': 7.4.6
       '@types/cross-spawn': 6.0.2
       cross-spawn: 7.0.3
       globby: 11.1.0
@@ -6383,42 +6450,19 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/components@7.4.4(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-tFOSu3IoAab/0aY2TY66Go0Nba7AB/+ZB9GFet+dxWypIKGLcPjyX2POIumJU4swzK+4IA8GxgDQ2itS6EOISQ==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-    dependencies:
-      '@radix-ui/react-select': 1.2.2(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-toolbar': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.4.4
-      '@storybook/csf': 0.1.0
-      '@storybook/global': 5.0.0
-      '@storybook/theming': 7.4.4(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.4
-      memoizerific: 1.11.3
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-      use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0)
-      util-deprecate: 1.0.2
-    transitivePeerDependencies:
-      - '@types/react'
-      - '@types/react-dom'
-    dev: true
-
-  /@storybook/components@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-boskkfvMBB8CFYY9+1ofFNyKrdWXTY/ghzt7oK80dz6f2Eseo/WXK3OsCdCq5vWbLRCdbgJ8zXG8pAFi4yBsxA==}
+  /@storybook/components@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-nIRBhewAgrJJVafyCzuaLx1l+YOfvvD5dOZ0JxZsxJsefOdw1jFpUqUZ5fIpQ2moyvrR0mAUFw378rBfMdHz5Q==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
       '@radix-ui/react-select': 1.2.2(react-dom@18.2.0)(react@18.2.0)
       '@radix-ui/react-toolbar': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.4.5
+      '@storybook/client-logger': 7.4.6
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -6429,19 +6473,19 @@ packages:
       - '@types/react-dom'
     dev: true
 
-  /@storybook/core-client@7.4.5:
-    resolution: {integrity: sha512-d/qiCUZeOKY0HX/YmomxlccxJ2NKC3ttRrAsAXzJGypClKabv20X+qbeO/E7Kp5UQxIEJx1wuwJPcnlCvjgPDA==}
+  /@storybook/core-client@7.4.6:
+    resolution: {integrity: sha512-tfgxAHeCvMcs6DsVgtb4hQSDaCHeAPJOsoyhb47eDQfk4OmxzriM0qWucJV5DePSMi+KutX/rN2u0JxfOuN68g==}
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/preview-api': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/preview-api': 7.4.6
     dev: true
 
-  /@storybook/core-common@7.4.5:
-    resolution: {integrity: sha512-c4pBuILMD4YhSpJ+QpKtsUZpK+/rfolwOvzXfJwlN5EpYzMz6FjVR/LyX0cCT2YLI3X5YWRoCdvMxy5Aeryb8g==}
+  /@storybook/core-common@7.4.6:
+    resolution: {integrity: sha512-05MJFmOM86qvTLtgDskokIFz9txe0Lbhq4L3by1FtF0GwgH+p+W6I94KI7c6ANER+kVZkXQZhiRzwBFnVTW+Cg==}
     dependencies:
-      '@storybook/core-events': 7.4.5
-      '@storybook/node-logger': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/core-events': 7.4.6
+      '@storybook/node-logger': 7.4.6
+      '@storybook/types': 7.4.6
       '@types/find-cache-dir': 3.2.1
       '@types/node': 16.18.46
       '@types/node-fetch': 2.6.4
@@ -6467,36 +6511,30 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/core-events@7.4.4:
-    resolution: {integrity: sha512-kOf4I/a1XC9CaGFwJG5WR2KnkwrOkWX68TLh7OlelKxdl/WjxA4zfzaFPC/8zyCSLdGFLPKNqr1w+ezkb+9Irw==}
+  /@storybook/core-events@7.4.6:
+    resolution: {integrity: sha512-r5vrE+32lwrJh1NGFr1a0mWjvxo7q8FXYShylcwRWpacmL5NTtLkrXOoJSeGvJ4yKNYkvxQFtOPId4lzDxa32w==}
     dependencies:
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/core-events@7.4.5:
-    resolution: {integrity: sha512-Jzy/adSC95saYCZlgXE5j7jmiMLAXYpnBFBxEtBdXwSWEBb0zt21n1nyWBEAv9s/k2gqDXlPHKHeL5Mn6y40zA==}
-    dependencies:
-      ts-dedent: 2.2.0
-    dev: true
-
-  /@storybook/core-server@7.4.5:
-    resolution: {integrity: sha512-cW+Qx9Ls823577bd/s9Kv4M1MdKS8mkk6/+nYbwtAwH3hkdlb077rlk/ue0X4O9NZmCrtaJ84KNrBkeDUdFyLQ==}
+  /@storybook/core-server@7.4.6:
+    resolution: {integrity: sha512-jqmRTGCJ1W0WReImivkisPVaLFT5sjtLnFoAk0feHp6QS5j7EYOPN7CYzliyQmARWTLUEXOVaFf3VD6nJZQhJQ==}
     dependencies:
       '@aw-web-design/x-default-browser': 1.4.126
       '@discoveryjs/json-ext': 0.5.7
-      '@storybook/builder-manager': 7.4.5
-      '@storybook/channels': 7.4.5
-      '@storybook/core-common': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/builder-manager': 7.4.6
+      '@storybook/channels': 7.4.6
+      '@storybook/core-common': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/csf': 0.1.0
-      '@storybook/csf-tools': 7.4.5
+      '@storybook/csf-tools': 7.4.6
       '@storybook/docs-mdx': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/manager': 7.4.5
-      '@storybook/node-logger': 7.4.5
-      '@storybook/preview-api': 7.4.5
-      '@storybook/telemetry': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/manager': 7.4.6
+      '@storybook/node-logger': 7.4.6
+      '@storybook/preview-api': 7.4.6
+      '@storybook/telemetry': 7.4.6
+      '@storybook/types': 7.4.6
       '@types/detect-port': 1.3.2
       '@types/node': 16.18.46
       '@types/pretty-hrtime': 1.0.1
@@ -6516,7 +6554,6 @@ packages:
       prompts: 2.4.2
       read-pkg-up: 7.0.1
       semver: 7.5.4
-      serve-favicon: 2.5.0
       telejson: 7.2.0
       tiny-invariant: 1.3.1
       ts-dedent: 2.2.0
@@ -6531,24 +6568,24 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@storybook/csf-plugin@7.4.5:
-    resolution: {integrity: sha512-8p3AnwIm3xXtQhiF7OQ0rBiP/Pn5OCMHRiT4FytRnNimGaw7gxRZ2xzM608QZHQ4A8rHfmgoM2FTwgxdC15ulA==}
+  /@storybook/csf-plugin@7.4.6:
+    resolution: {integrity: sha512-yi7Qa4NSqKOyiJTWCxlB0ih2ijXq6oY5qZKW6MuMMBP14xJNRGLbH5KabpfXgN2T7YECcOWG1uWaGj2veJb1KA==}
     dependencies:
-      '@storybook/csf-tools': 7.4.5
+      '@storybook/csf-tools': 7.4.6
       unplugin: 1.4.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/csf-tools@7.4.5:
-    resolution: {integrity: sha512-xbm5HGYvlwF0Efivx37v9rO7Exel1/Tdb/Yv/vXn4D/hQeljNVLNz4Bomfy4EQ207rRsrGDSOHEhLUbHDimnxg==}
+  /@storybook/csf-tools@7.4.6:
+    resolution: {integrity: sha512-ocKpcIUtTBy6hlLY34RUFQyX403cWpB2gGfqvkHbpGe2BQj7EyV0zpWnjsfVxvw+M9OWlCdxHWDOPUgXM33ELw==}
     dependencies:
       '@babel/generator': 7.22.10
       '@babel/parser': 7.22.16
       '@babel/traverse': 7.22.11
       '@babel/types': 7.22.17
       '@storybook/csf': 0.1.0
-      '@storybook/types': 7.4.5
+      '@storybook/types': 7.4.6
       fs-extra: 11.1.1
       recast: 0.23.1
       ts-dedent: 2.2.0
@@ -6566,12 +6603,12 @@ packages:
     resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==}
     dev: true
 
-  /@storybook/docs-tools@7.4.5:
-    resolution: {integrity: sha512-ctK+yGb2nvWISSvCCzj3ZhDaAb7I2BLjbxuBGTyNPvl4V9UQ9LBYzdJwR50q+DfscxdwSHMSOE/0OnzmJdaSJA==}
+  /@storybook/docs-tools@7.4.6:
+    resolution: {integrity: sha512-nZj1L/8WwKWWJ41FW4MaKGajZUtrhnr9UwflRCkQJaWhAKmDfOb5M5TqI93uCOULpFPOm5wpoMBz2IHInQ2Lrg==}
     dependencies:
-      '@storybook/core-common': 7.4.5
-      '@storybook/preview-api': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/core-common': 7.4.6
+      '@storybook/preview-api': 7.4.6
+      '@storybook/types': 7.4.6
       '@types/doctrine': 0.0.3
       doctrine: 3.0.0
       lodash: 4.17.21
@@ -6590,21 +6627,21 @@ packages:
     resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
     dev: true
 
-  /@storybook/instrumenter@7.4.5:
-    resolution: {integrity: sha512-VLFOcmG75QhWa7MtmfEybIJEz5oT2Ry8xAy/pIVhQwyBaeW0kRT0MHWkixRTtWQmJs/78FmHE3FlgMnqpa5JoA==}
+  /@storybook/instrumenter@7.4.6:
+    resolution: {integrity: sha512-K5atRoVFCl6HEgkSxIbwygpzgE/iROc7BrtJ3z3a7E70sanFr6Jxt6Egu6fz2QkL3ef4EWpXMnle2vhEfG29pA==}
     dependencies:
-      '@storybook/channels': 7.4.5
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/channels': 7.4.6
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.4.5
+      '@storybook/preview-api': 7.4.6
     dev: true
 
-  /@storybook/jest@0.2.2(vitest@0.34.5):
-    resolution: {integrity: sha512-PUfp9WoqUA8NdAmiz3UahUsyAMr+g1Dv3BB0fqJZsE2IuE5o1Mgsv4iLGzFm+ohcQLIDQvwvvbQIpxe8eY7TNw==}
+  /@storybook/jest@0.2.3(vitest@0.34.6):
+    resolution: {integrity: sha512-ov5izrmbAFObzKeh9AOC5MlmFxAcf0o5i6YFGae9sDx6DGh6alXsRM+chIbucVkUwVHVlSzdfbLDEFGY/ShaYw==}
     dependencies:
       '@storybook/expect': 28.1.3-5
-      '@testing-library/jest-dom': 6.1.2(@types/jest@28.1.3)(vitest@0.34.5)
+      '@testing-library/jest-dom': 6.1.2(@types/jest@28.1.3)(vitest@0.34.6)
       '@types/jest': 28.1.3
       jest-mock: 27.5.1
     transitivePeerDependencies:
@@ -6613,20 +6650,20 @@ packages:
       - vitest
     dev: true
 
-  /@storybook/manager-api@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-8Hdh5Tutet8xRy2fAknczfvpshz09eVnLd8m34vcFceUOYvEnvDbWerufhlEzovsF4v7U32uqbDHKdKTamWEQQ==}
+  /@storybook/manager-api@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-inrm3DIbCp8wjXSN/wK6e6i2ysQ/IEmtC7IN0OJ7vdrp+USCooPT448SQTUmVctUGCFmOU3fxXByq8g77oIi7w==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/channels': 7.4.5
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/channels': 7.4.6
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/router': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/router': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       dequal: 2.0.3
       lodash: 4.17.21
       memoizerific: 1.11.3
@@ -6638,31 +6675,31 @@ packages:
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/manager@7.4.5:
-    resolution: {integrity: sha512-yoqVktWzzC0f8cXsxErOEUfT+VFfWV/W7soytIPQuJFqNaq+BqR5A7WCeoY7BIv3mdpRjo4GKwerCsgoHYeHhg==}
+  /@storybook/manager@7.4.6:
+    resolution: {integrity: sha512-kA1hUDxpn1i2SO9OinvLvVXDeL4xgJkModp+pbE8IXv4NJWReNq1ecMeQCzPLS3Sil2gnrullQ9uYXsnZ9bxxA==}
     dev: true
 
   /@storybook/mdx2-csf@1.0.0:
     resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==}
     dev: true
 
-  /@storybook/node-logger@7.4.5:
-    resolution: {integrity: sha512-fJSykphbryuEYj1qihbaTH5oOzD4NkptRxyf2uyBrpgkr5tCTq9d7GHheqaBuIdi513dsjlcIR7z5iHxW7ZD+Q==}
+  /@storybook/node-logger@7.4.6:
+    resolution: {integrity: sha512-djZb310Q27GviDug1XBv0jOEDLCiwr4hhDE0aifCEKZpfNCi/EaP31nbWimFzZwxu4hE/YAPWExzScruR1zw9Q==}
     dev: true
 
-  /@storybook/postinstall@7.4.5:
-    resolution: {integrity: sha512-MWRjnKkUpEe2VkHNNpv3zkuMvxM2Zu9DMxFENQaEmhqUHkIFh5klfFwzhSBRexVLzIh7DA1p7mntIpY5A6lh+Q==}
+  /@storybook/postinstall@7.4.6:
+    resolution: {integrity: sha512-TqI5BucPAGRWrkh55BYiG2/gHLFtC0In4cuu0GsUzB/1jc4i51npLRorCwhmT7r7YliGl5F7JaP0Bni/qHN3Lg==}
     dev: true
 
-  /@storybook/preview-api@7.4.5:
-    resolution: {integrity: sha512-6xXQZPyilkGVddfZBI7tMbMMgOyIoZTYgTnwSPTMsXxO0f0TvtNDmGdwhn0I1nREHKfiQGpcQe6gwddEMnGtSg==}
+  /@storybook/preview-api@7.4.6:
+    resolution: {integrity: sha512-byUS/Opt3ytWD4cWz3sNEKw5Yks8MkQgRN+GDSyIomaEAQkLAM0rchPC0MYjwCeUSecV7IIQweNX5RbV4a34BA==}
     dependencies:
-      '@storybook/channels': 7.4.5
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-events': 7.4.5
+      '@storybook/channels': 7.4.6
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-events': 7.4.6
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/types': 7.4.5
+      '@storybook/types': 7.4.6
       '@types/qs': 6.9.7
       dequal: 2.0.3
       lodash: 4.17.21
@@ -6673,12 +6710,12 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/preview@7.4.5:
-    resolution: {integrity: sha512-hCVFoPJP0d7vFCJKaWEsDMa6LcRFcEikQ8Cy6Vo+trS8xXwvwE+vIBqyuPozl4O/MYD9iOlzjgZFNwaUUgX0Jg==}
+  /@storybook/preview@7.4.6:
+    resolution: {integrity: sha512-2RPXusJ4CTDrIipIKKvbotD7fP0+8VzoFjImunflIrzN9rni+2rq5eMjqlXAaB+77w064zIR4uDUzI9fxsMDeQ==}
     dev: true
 
-  /@storybook/react-dom-shim@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-/hGe8yuiWbT7L3ZsllmJPgxT9MEQE3k23FhliyKx6IGHsWoYaEsPYPZ9tygqtKY8RpqqMUKWz8+kbO79zUxaoQ==}
+  /@storybook/react-dom-shim@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-DSq8l9FDocUF1ooVI+TF83pddj1LynE/Hv0/y8XZhc3IgJ/HkuOQuUmfz29ezgfAi9gFYUR8raTIBi3/xdoRmw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6687,25 +6724,25 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/react-vite@7.4.5(react-dom@18.2.0)(react@18.2.0)(rollup@3.29.4)(typescript@5.2.2)(vite@4.4.9):
-    resolution: {integrity: sha512-VfEktqZlSiAcM0oqUnXvQDIFM/G3pOZSW9VCcdQp2NWbsG/UVH42++ZkT0qJmQtW+Kkr8mTofLK5H1v5si5Z1A==}
+  /@storybook/react-vite@7.4.6(react-dom@18.2.0)(react@18.2.0)(rollup@4.0.2)(typescript@5.2.2)(vite@4.4.11):
+    resolution: {integrity: sha512-jkjnrf3FxzR5wcmebXRPflrsM4WIDjWyW/NVFJwxi5PeIOk7fE7/QAPrm4NFRUu2Q7DeuH3oLKsw8bigvUI9RA==}
     engines: {node: '>=16'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
       vite: ^3.0.0 || ^4.0.0
     dependencies:
-      '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.2.2)(vite@4.4.9)
-      '@rollup/pluginutils': 5.0.4(rollup@3.29.4)
-      '@storybook/builder-vite': 7.4.5(typescript@5.2.2)(vite@4.4.9)
-      '@storybook/react': 7.4.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
-      '@vitejs/plugin-react': 3.1.0(vite@4.4.9)
+      '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.2.2)(vite@4.4.11)
+      '@rollup/pluginutils': 5.0.5(rollup@4.0.2)
+      '@storybook/builder-vite': 7.4.6(typescript@5.2.2)(vite@4.4.11)
+      '@storybook/react': 7.4.6(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)
+      '@vitejs/plugin-react': 3.1.0(vite@4.4.11)
       ast-types: 0.14.2
       magic-string: 0.30.3
       react: 18.2.0
       react-docgen: 6.0.0-alpha.3
       react-dom: 18.2.0(react@18.2.0)
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - encoding
@@ -6715,8 +6752,8 @@ packages:
       - vite-plugin-glimmerx
     dev: true
 
-  /@storybook/react@7.4.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2):
-    resolution: {integrity: sha512-Tiylrs3uFO8QSvH1w3ueSxlAgh2fteH0edRVKaX01M/h47+QqEiZqq/dYkVDvLHngF+CCCwE3OY8nNe6L14Xkw==}
+  /@storybook/react@7.4.6(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-w0dVo64baFFPTGpUOWFqkKsu6pQincoymegSNgqaBd5DxEyMDRiRoTWSJHMKE9BwgE8SyWhRkP1ak1mkccSOhQ==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -6726,13 +6763,13 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-client': 7.4.5
-      '@storybook/docs-tools': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-client': 7.4.6
+      '@storybook/docs-tools': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.4.5
-      '@storybook/react-dom-shim': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/preview-api': 7.4.6
+      '@storybook/react-dom-shim': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       '@types/escodegen': 0.0.6
       '@types/estree': 0.0.51
       '@types/node': 16.18.46
@@ -6755,27 +6792,27 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/router@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-IM4IhiPiXsx3FAUeUOAB47uiuUS8Yd37VQcNlXLBO28GgHoTSYOrjS+VTGLIV5cAGKr8+H5pFB+q35BnlFUpkQ==}
+  /@storybook/router@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-Vl1esrHkcHxDKqc+HY7+6JQpBPW3zYvGk0cQ2rxVMhWdLZTAz1hss9DqzN9tFnPyfn0a1Q77EpMySkUrvWKKNQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/client-logger': 7.4.5
+      '@storybook/client-logger': 7.4.6
       memoizerific: 1.11.3
       qs: 6.11.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/source-loader@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-ieo/aPgIXAJfg2raDtsboX43IXiXYHDm0MSXvNXoFE7F1jtRe7gXRi8z7O9xTX4hlIuYea0+kHe+198adgLlWA==}
+  /@storybook/source-loader@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-tBso55luaKIsZmIsgYyT7HJcjbgjxf0pdzbYqdThZhY3oSl3d56xbcFDCWW+yWjFONuFY8RGPCT7iGywwmaBdQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
       '@storybook/csf': 0.1.0
-      '@storybook/types': 7.4.5
+      '@storybook/types': 7.4.6
       estraverse: 5.3.0
       lodash: 4.17.21
       prettier: 2.8.8
@@ -6783,12 +6820,12 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/telemetry@7.4.5:
-    resolution: {integrity: sha512-JbhQXZF5sqS2c7Cf+vAtuKTdTSBDco+liUP2UGQFjqdacTRLVzxyj+YY2UH4aAQn7wpmnQ67iHnqFp0+fdYmAA==}
+  /@storybook/telemetry@7.4.6:
+    resolution: {integrity: sha512-c8p/C1NIH8EMBviZkBCx8MMDk6rrITJ+b29DEp5MaWSRlklIVyhGiC4RPIRv6sxJwlD41PnqWVFtfu2j2eXLdQ==}
     dependencies:
-      '@storybook/client-logger': 7.4.5
-      '@storybook/core-common': 7.4.5
-      '@storybook/csf-tools': 7.4.5
+      '@storybook/client-logger': 7.4.6
+      '@storybook/core-common': 7.4.6
+      '@storybook/csf-tools': 7.4.6
       chalk: 4.1.2
       detect-package-manager: 2.0.1
       fetch-retry: 5.0.4
@@ -6799,76 +6836,53 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/testing-library@0.2.1:
-    resolution: {integrity: sha512-AdbfLCm1C2nEFrhA3ScdicfW6Fjcorehr6RlGwECMiWwaXisnP971Wd4psqtWxlAqQo4tYBZ0f6rJ3J78JLtsg==}
+  /@storybook/testing-library@0.2.2:
+    resolution: {integrity: sha512-L8sXFJUHmrlyU2BsWWZGuAjv39Jl1uAqUHdxmN42JY15M4+XCMjGlArdCCjDe1wpTSW6USYISA9axjZojgtvnw==}
     dependencies:
       '@testing-library/dom': 9.2.0
       '@testing-library/user-event': 14.4.3(@testing-library/dom@9.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/theming@7.4.4(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-ABIwLRUj2IZKMGxKq+fCCFcY7w52P1a+q8j7qrlELaTe4M74K6rwTgRF0/AFgWeiGRkNuA7z8DjQ73xQLoLqUg==}
+  /@storybook/theming@7.4.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-HW77iJ9ptCMqhoBOYFjRQw7VBap+38fkJGHP5KylEJCyYCgIAm2dEcQmtWpMVYFssSGcb6djfbtAMhYU4TL4Iw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
       '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0)
-      '@storybook/client-logger': 7.4.4
+      '@storybook/client-logger': 7.4.6
       '@storybook/global': 5.0.0
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/theming@7.4.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-QSIJDIMzOegzlhubIBaYIovf4mlf+AVL0SmQOskPS8GZ6s9t77yUUI6gZTEjO+S4eB3djXRsfTTijQ8+z4XmRA==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+  /@storybook/types@7.4.6:
+    resolution: {integrity: sha512-6QLXtMVsFZFpzPkdGWsu/iuc8na9dnS67AMOBKm5qCLPwtUJOYkwhMdFRSSeJthLRpzV7JLAL8Kwvl7MFP3QSw==}
     dependencies:
-      '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0)
-      '@storybook/client-logger': 7.4.5
-      '@storybook/global': 5.0.0
-      memoizerific: 1.11.3
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@storybook/types@7.4.4:
-    resolution: {integrity: sha512-B0VdgGb1XGEb9g3UuEd9xANCIhR3anvA3w0uYSG+7uMOflnEawwZksTSxvvoGM2hx9vC4pNT4Fci9sEC903UkA==}
-    dependencies:
-      '@storybook/channels': 7.4.4
-      '@types/babel__core': 7.20.0
-      '@types/express': 4.17.17
-      file-system-cache: 2.3.0
-    dev: true
-
-  /@storybook/types@7.4.5:
-    resolution: {integrity: sha512-DTWFNjfRTpncjufDoUs0QnNkgHG2qThGKWL1D6sO18cYI02zWPyHWD8/cbqlvtT7XIGe3s1iUEfCTdU5GcwWBA==}
-    dependencies:
-      '@storybook/channels': 7.4.5
+      '@storybook/channels': 7.4.6
       '@types/babel__core': 7.20.0
       '@types/express': 4.17.17
       file-system-cache: 2.3.0
     dev: true
 
-  /@storybook/vue3-vite@7.4.5(@vue/compiler-core@3.3.4)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(vite@4.4.9)(vue@3.3.4):
-    resolution: {integrity: sha512-hNuzSd7EAGpLNGekKjOfuMpir1CpMbSvro4q+04G34CGw2O6awoQKqE+gaOeAHIsSPffio5eeaBR1nKjoKYEog==}
+  /@storybook/vue3-vite@7.4.6(@vue/compiler-core@3.3.4)(react-dom@18.2.0)(react@18.2.0)(typescript@5.2.2)(vite@4.4.11)(vue@3.3.4):
+    resolution: {integrity: sha512-r/mUDdCifpN99Cqmvm7IvPZGnur7lYiTxbQPhV8NdRBpQGxm3JC0life9yIvvHV9mYRCjn5MEzC65zWx03Nzig==}
     engines: {node: ^14.18 || >=16}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
       vite: ^3.0.0 || ^4.0.0
     dependencies:
-      '@storybook/builder-vite': 7.4.5(typescript@5.2.2)(vite@4.4.9)
-      '@storybook/core-server': 7.4.5
-      '@storybook/vue3': 7.4.5(@vue/compiler-core@3.3.4)(vue@3.3.4)
-      '@vitejs/plugin-vue': 4.3.4(vite@4.4.9)(vue@3.3.4)
+      '@storybook/builder-vite': 7.4.6(typescript@5.2.2)(vite@4.4.11)
+      '@storybook/core-server': 7.4.6
+      '@storybook/vue3': 7.4.6(@vue/compiler-core@3.3.4)(vue@3.3.4)
+      '@vitejs/plugin-vue': 4.4.0(vite@4.4.11)(vue@3.3.4)
       magic-string: 0.30.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
       vue-docgen-api: 4.64.1(vue@3.3.4)
     transitivePeerDependencies:
       - '@preact/preset-vite'
@@ -6882,30 +6896,30 @@ packages:
       - vue
     dev: true
 
-  /@storybook/vue3@7.4.5(@vue/compiler-core@3.3.4)(vue@3.3.4):
-    resolution: {integrity: sha512-9vmGSg+jwpTYeBneC3XAL5zJW7/kfA/3tXNfIOkqA4oJ087TBoo5XztzbtT6pSNq8fB9AY8VyPTG8ZE5IaJ4xQ==}
+  /@storybook/vue3@7.4.6(@vue/compiler-core@3.3.4)(vue@3.3.4):
+    resolution: {integrity: sha512-Azv/GhmPlAUy8UbXZHKubrBlKhGimuJTT2O6zUvIzggR6sJdsRmdWaEv2S90ZpMBkVYyyM9oKS1fZ4eKi/Ds8g==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       '@vue/compiler-core': ^3.0.0
       vue: ^3.0.0
     dependencies:
-      '@storybook/core-client': 7.4.5
-      '@storybook/docs-tools': 7.4.5
+      '@storybook/core-client': 7.4.6
+      '@storybook/docs-tools': 7.4.6
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.4.5
-      '@storybook/types': 7.4.5
+      '@storybook/preview-api': 7.4.6
+      '@storybook/types': 7.4.6
       '@vue/compiler-core': 3.3.4
       lodash: 4.17.21
       ts-dedent: 2.2.0
       type-fest: 2.19.0
       vue: 3.3.4
-      vue-component-type-helpers: 1.8.15
+      vue-component-type-helpers: 1.8.18
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  /@swc/cli@0.1.62(@swc/core@1.3.90)(chokidar@3.5.3):
+  /@swc/cli@0.1.62(@swc/core@1.3.92)(chokidar@3.5.3):
     resolution: {integrity: sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==}
     engines: {node: '>= 12.13'}
     hasBin: true
@@ -6917,7 +6931,7 @@ packages:
         optional: true
     dependencies:
       '@mole-inc/bin-wrapper': 8.0.1
-      '@swc/core': 1.3.90
+      '@swc/core': 1.3.92
       chokidar: 3.5.3
       commander: 7.2.0
       fast-glob: 3.3.1
@@ -6946,8 +6960,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-arm64@1.3.90:
-    resolution: {integrity: sha512-he0w74HvcoufE6CZrB/U/VGVbc7021IQvYrn1geMACnq/OqMBqjdczNtdNfJAy87LZ4AOUjHDKEIjsZZu7o8nQ==}
+  /@swc/core-darwin-arm64@1.3.92:
+    resolution: {integrity: sha512-v7PqZUBtIF6Q5Cp48gqUiG8zQQnEICpnfNdoiY3xjQAglCGIQCjJIDjreZBoeZQZspB27lQN4eZ43CX18+2SnA==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [darwin]
@@ -6963,8 +6977,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-x64@1.3.90:
-    resolution: {integrity: sha512-hKNM0Ix0qMlAamPe0HUfaAhQVbZEL5uK6Iw8v9ew0FtVB4v7EifQ9n41wh+yCj0CjcHBPEBbQU0P6mNTxJu/RQ==}
+  /@swc/core-darwin-x64@1.3.92:
+    resolution: {integrity: sha512-Q3XIgQfXyxxxms3bPN+xGgvwk0TtG9l89IomApu+yTKzaIIlf051mS+lGngjnh9L0aUiCp6ICyjDLtutWP54fw==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [darwin]
@@ -6991,8 +7005,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm-gnueabihf@1.3.90:
-    resolution: {integrity: sha512-HumvtrqTWE8rlFuKt7If0ZL7145H/jVc4AeziVjcd+/ajpqub7IyfrLCYd5PmKMtfeSVDMsxjG0BJ0HLRxrTJA==}
+  /@swc/core-linux-arm-gnueabihf@1.3.92:
+    resolution: {integrity: sha512-tnOCoCpNVXC+0FCfG84PBZJyLlz0Vfj9MQhyhCvlJz9hQmvpf8nTdKH7RHrOn8VfxtUBLdVi80dXgIFgbvl7qA==}
     engines: {node: '>=10'}
     cpu: [arm]
     os: [linux]
@@ -7008,8 +7022,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-gnu@1.3.90:
-    resolution: {integrity: sha512-tA7DqCS7YCwngwXZQeqQhhMm8BbydpaABw8Z/EDQ7KPK1iZ1rNjZw+aWvSpmNmEGmH1RmQ9QDS9mGRDp0faAeg==}
+  /@swc/core-linux-arm64-gnu@1.3.92:
+    resolution: {integrity: sha512-lFfGhX32w8h1j74Iyz0Wv7JByXIwX11OE9UxG+oT7lG0RyXkF4zKyxP8EoxfLrDXse4Oop434p95e3UNC3IfCw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7025,8 +7039,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-musl@1.3.90:
-    resolution: {integrity: sha512-p2Vtid5BZA36fJkNUwk5HP+HJlKgTru+Ghna7pRe45ghKkkRIUk3fhkgudEvfKfhT+3AvP+GTVQ+T9k0gc9S8w==}
+  /@swc/core-linux-arm64-musl@1.3.92:
+    resolution: {integrity: sha512-rOZtRcLj57MSAbiecMsqjzBcZDuaCZ8F6l6JDwGkQ7u1NYR57cqF0QDyU7RKS1Jq27Z/Vg21z5cwqoH5fLN+Sg==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7042,8 +7056,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-gnu@1.3.90:
-    resolution: {integrity: sha512-J6pDtWaulYGXuANERuvv4CqmUbZOQrRZBCRQGZQJ6a86RWpesZqckBelnYx48wYmkgvMkF95Y3xbI3WTfoSHzw==}
+  /@swc/core-linux-x64-gnu@1.3.92:
+    resolution: {integrity: sha512-qptoMGnBL6v89x/Qpn+l1TH1Y0ed+v0qhNfAEVzZvCvzEMTFXphhlhYbDdpxbzRmCjH6GOGq7Y+xrWt9T1/ARg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7059,8 +7073,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-musl@1.3.90:
-    resolution: {integrity: sha512-3Gh6EA3+0K+l3MqnRON7h5bZ32xLmfcVM6QiHHJ9dBttq7YOEeEoMOCdIPMaQxJmK1VfLgZCsPYRd66MhvUSkw==}
+  /@swc/core-linux-x64-musl@1.3.92:
+    resolution: {integrity: sha512-g2KrJ43bZkCZHH4zsIV5ErojuV1OIpUHaEyW1gf7JWKaFBpWYVyubzFPvPkjcxHGLbMsEzO7w/NVfxtGMlFH/Q==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7076,8 +7090,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-arm64-msvc@1.3.90:
-    resolution: {integrity: sha512-BNaw/iJloDyaNOFV23Sr53ULlnbmzSoerTJ10v0TjSZOEIpsS0Rw6xOK1iI0voDJnRXeZeWRSxEC9DhefNtN/g==}
+  /@swc/core-win32-arm64-msvc@1.3.92:
+    resolution: {integrity: sha512-3MCRGPAYDoQ8Yyd3WsCMc8eFSyKXY5kQLyg/R5zEqA0uthomo0m0F5/fxAJMZGaSdYkU1DgF73ctOWOf+Z/EzQ==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [win32]
@@ -7093,8 +7107,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-ia32-msvc@1.3.90:
-    resolution: {integrity: sha512-SiyTethWAheE/JbxXCukAAciU//PLcmVZ2ME92MRuLMLmOhrwksjbaa7ukj9WEF3LWrherhSqTXnpj3VC1l/qw==}
+  /@swc/core-win32-ia32-msvc@1.3.92:
+    resolution: {integrity: sha512-zqTBKQhgfWm73SVGS8FKhFYDovyRl1f5dTX1IwSKynO0qHkRCqJwauFJv/yevkpJWsI2pFh03xsRs9HncTQKSA==}
     engines: {node: '>=10'}
     cpu: [ia32]
     os: [win32]
@@ -7110,16 +7124,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-x64-msvc@1.3.90:
-    resolution: {integrity: sha512-OpWAW5ljKcPJ3SQ0pUuKqYfwXv7ssIhVgrH9XP9ONtdgXKWZRL9hqJQkcL55FARw/gDjKanoCM47wsTNQL+ZZA==}
+  /@swc/core-win32-x64-msvc@1.3.92:
+    resolution: {integrity: sha512-41bE66ddr9o/Fi1FBh0sHdaKdENPTuDpv1IFHxSg0dJyM/jX8LbkjnpdInYXHBxhcLVAPraVRrNsC4SaoPw2Pg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@swc/core@1.3.90:
-    resolution: {integrity: sha512-wptBxP4PldOnhmyDVj8qUcn++GRqyw1qc9wOTGtPNHz8cpuTfdfIgYGlhI4La0UYqecuaaIfLfokyuNePOMHPg==}
+  /@swc/core@1.3.92:
+    resolution: {integrity: sha512-vx0vUrf4YTEw59njOJ46Ha5i0cZTMYdRHQ7KXU29efN1MxcmJH2RajWLPlvQarOP1ab9iv9cApD7SMchDyx2vA==}
     engines: {node: '>=10'}
     requiresBuild: true
     peerDependencies:
@@ -7131,28 +7145,28 @@ packages:
       '@swc/counter': 0.1.1
       '@swc/types': 0.1.5
     optionalDependencies:
-      '@swc/core-darwin-arm64': 1.3.90
-      '@swc/core-darwin-x64': 1.3.90
-      '@swc/core-linux-arm-gnueabihf': 1.3.90
-      '@swc/core-linux-arm64-gnu': 1.3.90
-      '@swc/core-linux-arm64-musl': 1.3.90
-      '@swc/core-linux-x64-gnu': 1.3.90
-      '@swc/core-linux-x64-musl': 1.3.90
-      '@swc/core-win32-arm64-msvc': 1.3.90
-      '@swc/core-win32-ia32-msvc': 1.3.90
-      '@swc/core-win32-x64-msvc': 1.3.90
+      '@swc/core-darwin-arm64': 1.3.92
+      '@swc/core-darwin-x64': 1.3.92
+      '@swc/core-linux-arm-gnueabihf': 1.3.92
+      '@swc/core-linux-arm64-gnu': 1.3.92
+      '@swc/core-linux-arm64-musl': 1.3.92
+      '@swc/core-linux-x64-gnu': 1.3.92
+      '@swc/core-linux-x64-musl': 1.3.92
+      '@swc/core-win32-arm64-msvc': 1.3.92
+      '@swc/core-win32-ia32-msvc': 1.3.92
+      '@swc/core-win32-x64-msvc': 1.3.92
 
   /@swc/counter@0.1.1:
     resolution: {integrity: sha512-xVRaR4u9hcYjFvcSg71Lz5Bo4//CyjAAfMxa7UsaDSYxAshflUkVJWiyVWrfxC59z2kP1IzI4/1BEpnhI9o3Mw==}
 
-  /@swc/jest@0.2.29(@swc/core@1.3.90):
+  /@swc/jest@0.2.29(@swc/core@1.3.92):
     resolution: {integrity: sha512-8reh5RvHBsSikDC3WGCd5ZTd2BXKkyOdK7QwynrCH58jk2cQFhhHhFBg/jvnWZehUQe/EoOImLENc9/DwbBFow==}
     engines: {npm: '>= 7.0.0'}
     peerDependencies:
       '@swc/core': '*'
     dependencies:
       '@jest/create-cache-key-function': 27.5.1
-      '@swc/core': 1.3.90
+      '@swc/core': 1.3.92
       jsonc-parser: 3.2.0
     dev: true
 
@@ -7329,7 +7343,7 @@ packages:
       pretty-format: 27.5.1
     dev: true
 
-  /@testing-library/jest-dom@6.1.2(@types/jest@28.1.3)(vitest@0.34.5):
+  /@testing-library/jest-dom@6.1.2(@types/jest@28.1.3)(vitest@0.34.6):
     resolution: {integrity: sha512-NP9jl1Q2qDDtx+cqogowtQtmgD2OVs37iMSIsTv5eN5ETRkf26Kj6ugVwA93/gZzzFWQAsgkKkcftDe91BJCkQ==}
     engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
     peerDependencies:
@@ -7356,7 +7370,7 @@ packages:
       dom-accessibility-api: 0.5.16
       lodash: 4.17.21
       redent: 3.0.0
-      vitest: 0.34.5(happy-dom@10.0.3)(sass@1.68.0)(terser@5.20.0)
+      vitest: 0.34.6(happy-dom@10.0.3)(sass@1.69.1)(terser@5.21.0)
     dev: true
 
   /@testing-library/user-event@14.4.3(@testing-library/dom@9.2.0):
@@ -7404,7 +7418,7 @@ packages:
   /@types/accepts@1.3.5:
     resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/archiver@5.3.3:
@@ -7458,7 +7472,7 @@ packages:
     resolution: {integrity: sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==}
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/braces@3.0.1:
@@ -7470,7 +7484,7 @@ packages:
     dependencies:
       '@types/http-cache-semantics': 4.0.1
       '@types/keyv': 3.1.4
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       '@types/responselike': 1.0.0
     dev: false
 
@@ -7503,7 +7517,7 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/content-disposition@0.5.6:
@@ -7517,7 +7531,7 @@ packages:
   /@types/cross-spawn@6.0.2:
     resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/debug@4.1.7:
@@ -7571,7 +7585,7 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
     dev: true
@@ -7592,20 +7606,20 @@ packages:
   /@types/fluent-ffmpeg@2.1.22:
     resolution: {integrity: sha512-ZZPDDrDOb2Ahp5fxZzuw64f0rCcviv+SDuCyJ1PIF/UFn9wNHtb/bY8Dj/2nrbQ7SNsGI7gaO2wJVkkU2HBcMg==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/glob@7.2.0:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/graceful-fs@4.1.6:
     resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/hast@2.3.4:
@@ -7620,7 +7634,7 @@ packages:
   /@types/http-link-header@1.0.3:
     resolution: {integrity: sha512-y8HkoD/vyid+5MrJ3aas0FvU3/BVBGcyG9kgxL0Zn4JwstA8CglFPnrR0RuzOjRCXwqzL5uxWC2IO7Ub0rMU2A==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/istanbul-lib-coverage@2.0.4:
@@ -7664,7 +7678,7 @@ packages:
   /@types/jsdom@21.1.3:
     resolution: {integrity: sha512-1zzqSP+iHJYV4lB3lZhNBa012pubABkj9yG/GuXuf6LZH1cSPIJBqFDrm5JX65HHt6VOnNYdTui/0ySerRbMgA==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       '@types/tough-cookie': 4.0.2
       parse5: 7.1.2
     dev: true
@@ -7688,7 +7702,7 @@ packages:
   /@types/keyv@3.1.4:
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: false
 
   /@types/lodash@4.14.191:
@@ -7737,7 +7751,7 @@ packages:
   /@types/node-fetch@2.6.4:
     resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       form-data: 3.0.1
 
   /@types/node-fetch@3.0.3:
@@ -7754,13 +7768,15 @@ packages:
     resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==}
     dev: true
 
-  /@types/node@20.7.1:
-    resolution: {integrity: sha512-LT+OIXpp2kj4E2S/p91BMe+VgGX2+lfO+XTpfXhh+bCk2LkQtHZSub8ewFBMGP5ClysPjTDFa4sMI8Q3n4T0wg==}
+  /@types/node@20.8.4:
+    resolution: {integrity: sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==}
+    dependencies:
+      undici-types: 5.25.3
 
   /@types/nodemailer@6.4.11:
     resolution: {integrity: sha512-Ld2c0frwpGT4VseuoeboCXQ7UJIkK3X7Lx/4YsZEiUHtHsthWAOCYtf6PAiLhMtfwV0cWJRabLBS3+LD8x6Nrw==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/normalize-package-data@2.4.1:
@@ -7777,13 +7793,13 @@ packages:
     resolution: {integrity: sha512-U3L0c4eQA6lTSZRgW4LYfhKlR084Aw19akmYHrMdYzaqg9mQDfc2b/1iyqm9+1FJDEnVS5ONi5fxdDrB4/7CpQ==}
     dependencies:
       '@types/express': 4.17.17
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/oauth@0.9.2:
     resolution: {integrity: sha512-Nu3/abQ6yR9VlsCdX3aiGsWFkj6OJvJqDvg/36t8Gwf2mFXdBZXPDN3K+2yfeA6Lo2m1Q12F8Qil9TZ48nWhOQ==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/offscreencanvas@2019.3.0:
@@ -7796,10 +7812,10 @@ packages:
     requiresBuild: true
     dev: false
 
-  /@types/pg@8.10.3:
-    resolution: {integrity: sha512-BACzsw64lCZesclRpZGu55tnqgFAYcrCBP92xLh1KLypZLCOsvJTSTgaoFVTy3lCys/aZTQzfeDxtjwrvdzL2g==}
+  /@types/pg@8.10.4:
+    resolution: {integrity: sha512-6cxJPHzhlJxqAMkWl2w3KubTEM0UjGC0UrtIToa9J/CEuRFJ2bquKt+g9MhYBN9n1+U6UZZ8CW6Z4oLx/Tvh/w==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       pg-protocol: 1.6.0
       pg-types: 4.0.1
     dev: true
@@ -7823,7 +7839,7 @@ packages:
   /@types/qrcode@1.5.2:
     resolution: {integrity: sha512-W4KDz75m7rJjFbyCctzCtRzZUj+PrUHV+YjqDp50sSRezTbrtEAIq2iTzC6lISARl3qw+8IlcCyljdcVJE0Wug==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/qs@6.9.7:
@@ -7853,7 +7869,7 @@ packages:
   /@types/readdir-glob@1.1.1:
     resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/rename@1.0.5:
@@ -7863,7 +7879,7 @@ packages:
   /@types/responselike@1.0.0:
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: false
 
   /@types/sanitize-html@2.9.1:
@@ -7889,7 +7905,7 @@ packages:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/serviceworker@0.0.67:
@@ -7899,7 +7915,7 @@ packages:
   /@types/set-cookie-parser@2.4.3:
     resolution: {integrity: sha512-7QhnH7bi+6KAhBB+Auejz1uV9DHiopZqu7LfR/5gZZTkejJV5nYeZZpgfFoE0N8aDsXuiYpfKyfyMatCwQhyTQ==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/sharp@0.32.0:
@@ -7955,20 +7971,20 @@ packages:
     resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
     dev: true
 
-  /@types/uuid@9.0.4:
-    resolution: {integrity: sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==}
+  /@types/uuid@9.0.5:
+    resolution: {integrity: sha512-xfHdwa1FMJ082prjSJpoEI57GZITiQz10r3vEJCHa2khEFQjKy91aWKz6+zybzssCvXUwE1LQWgWVwZ4nYUvHQ==}
     dev: true
 
   /@types/vary@1.1.1:
     resolution: {integrity: sha512-XL8U62BpXBMMuFzFBYsWekQwo+dqcyN117IwFVMCkBCvc6HY1ODdRKNA0JHxnuTM5lX3kpqsnBH5OuEeXSN3aA==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/web-push@3.6.1:
     resolution: {integrity: sha512-Zu6Iju7c4IlE8I8eEeFLYRb7XFqvHFmWWAYr1cmug9EX3c6CDarxIXWN/GO0sxjbJLkHPwozUzp6cLdXsrq7Ew==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/webgl-ext@0.0.30:
@@ -7979,13 +7995,13 @@ packages:
   /@types/websocket@1.0.7:
     resolution: {integrity: sha512-62Omr8U0PO+hgjLCpPnMsmjh2/FRwIGOktZHyYAUzooEJotwkXHMp7vCacdYi8haxBNOiw9bc2HIHI+b/MPNjA==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/ws@8.5.6:
     resolution: {integrity: sha512-8B5EO9jLVCy+B58PLHvLDuOD8DRVMgQzq8d55SjLCOn9kqGyqOvy27exVaTio1q1nX5zLu8/6N0n2ThSxOM6tg==}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /@types/yargs-parser@21.0.0:
@@ -8008,12 +8024,12 @@ packages:
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
     requiresBuild: true
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
     dev: true
     optional: true
 
-  /@typescript-eslint/eslint-plugin@6.7.3(@typescript-eslint/parser@6.7.3)(eslint@8.50.0)(typescript@5.2.2):
-    resolution: {integrity: sha512-vntq452UHNltxsaaN+L9WyuMch8bMd9CqJ3zhzTPXXidwbf5mqqKCVXEuvRZUqLJSTLeWE65lQwyXsRGnXkCTA==}
+  /@typescript-eslint/eslint-plugin@6.7.5(@typescript-eslint/parser@6.7.5)(eslint@8.51.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-JhtAwTRhOUcP96D0Y6KYnwig/MRQbOoLGXTON2+LlyB/N35SP9j1boai2zzwXb7ypKELXMx3DVk9UTaEq1vHEw==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
@@ -8024,13 +8040,13 @@ packages:
         optional: true
     dependencies:
       '@eslint-community/regexpp': 4.6.2
-      '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.2.2)
-      '@typescript-eslint/scope-manager': 6.7.3
-      '@typescript-eslint/type-utils': 6.7.3(eslint@8.50.0)(typescript@5.2.2)
-      '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.2.2)
-      '@typescript-eslint/visitor-keys': 6.7.3
+      '@typescript-eslint/parser': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
+      '@typescript-eslint/scope-manager': 6.7.5
+      '@typescript-eslint/type-utils': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
+      '@typescript-eslint/utils': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
+      '@typescript-eslint/visitor-keys': 6.7.5
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.50.0
+      eslint: 8.51.0
       graphemer: 1.4.0
       ignore: 5.2.4
       natural-compare: 1.4.0
@@ -8041,8 +8057,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.2.2):
-    resolution: {integrity: sha512-TlutE+iep2o7R8Lf+yoer3zU6/0EAUc8QIBB3GYBc1KGz4c4TRm83xwXUZVPlZ6YCLss4r77jbu6j3sendJoiQ==}
+  /@typescript-eslint/parser@6.7.5(eslint@8.51.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-bIZVSGx2UME/lmhLcjdVc7ePBwn7CLqKarUBL4me1C5feOd663liTGjMBGVcGr+BhnSLeP4SgwdvNnnkbIdkCw==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -8051,27 +8067,27 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 6.7.3
-      '@typescript-eslint/types': 6.7.3
-      '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.2.2)
-      '@typescript-eslint/visitor-keys': 6.7.3
+      '@typescript-eslint/scope-manager': 6.7.5
+      '@typescript-eslint/types': 6.7.5
+      '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2)
+      '@typescript-eslint/visitor-keys': 6.7.5
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.50.0
+      eslint: 8.51.0
       typescript: 5.2.2
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/scope-manager@6.7.3:
-    resolution: {integrity: sha512-wOlo0QnEou9cHO2TdkJmzF7DFGvAKEnB82PuPNHpT8ZKKaZu6Bm63ugOTn9fXNJtvuDPanBc78lGUGGytJoVzQ==}
+  /@typescript-eslint/scope-manager@6.7.5:
+    resolution: {integrity: sha512-GAlk3eQIwWOJeb9F7MKQ6Jbah/vx1zETSDw8likab/eFcqkjSD7BI75SDAeC5N2L0MmConMoPvTsmkrg71+B1A==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.7.3
-      '@typescript-eslint/visitor-keys': 6.7.3
+      '@typescript-eslint/types': 6.7.5
+      '@typescript-eslint/visitor-keys': 6.7.5
     dev: true
 
-  /@typescript-eslint/type-utils@6.7.3(eslint@8.50.0)(typescript@5.2.2):
-    resolution: {integrity: sha512-Fc68K0aTDrKIBvLnKTZ5Pf3MXK495YErrbHb1R6aTpfK5OdSFj0rVN7ib6Tx6ePrZ2gsjLqr0s98NG7l96KSQw==}
+  /@typescript-eslint/type-utils@6.7.5(eslint@8.51.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-Gs0qos5wqxnQrvpYv+pf3XfcRXW6jiAn9zE/K+DlmYf6FcpxeNYN0AIETaPR7rHO4K2UY+D0CIbDP9Ut0U4m1g==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -8080,23 +8096,23 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.2.2)
-      '@typescript-eslint/utils': 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+      '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2)
+      '@typescript-eslint/utils': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.50.0
+      eslint: 8.51.0
       ts-api-utils: 1.0.1(typescript@5.2.2)
       typescript: 5.2.2
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/types@6.7.3:
-    resolution: {integrity: sha512-4g+de6roB2NFcfkZb439tigpAMnvEIg3rIjWQ+EM7IBaYt/CdJt6em9BJ4h4UpdgaBWdmx2iWsafHTrqmgIPNw==}
+  /@typescript-eslint/types@6.7.5:
+    resolution: {integrity: sha512-WboQBlOXtdj1tDFPyIthpKrUb+kZf2VroLZhxKa/VlwLlLyqv/PwUNgL30BlTVZV1Wu4Asu2mMYPqarSO4L5ZQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
-  /@typescript-eslint/typescript-estree@6.7.3(typescript@5.2.2):
-    resolution: {integrity: sha512-YLQ3tJoS4VxLFYHTw21oe1/vIZPRqAO91z6Uv0Ss2BKm/Ag7/RVQBcXTGcXhgJMdA4U+HrKuY5gWlJlvoaKZ5g==}
+  /@typescript-eslint/typescript-estree@6.7.5(typescript@5.2.2):
+    resolution: {integrity: sha512-NhJiJ4KdtwBIxrKl0BqG1Ur+uw7FiOnOThcYx9DpOGJ/Abc9z2xNzLeirCG02Ig3vkvrc2qFLmYSSsaITbKjlg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       typescript: '*'
@@ -8104,8 +8120,8 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 6.7.3
-      '@typescript-eslint/visitor-keys': 6.7.3
+      '@typescript-eslint/types': 6.7.5
+      '@typescript-eslint/visitor-keys': 6.7.5
       debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
@@ -8116,34 +8132,34 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/utils@6.7.3(eslint@8.50.0)(typescript@5.2.2):
-    resolution: {integrity: sha512-vzLkVder21GpWRrmSR9JxGZ5+ibIUSudXlW52qeKpzUEQhRSmyZiVDDj3crAth7+5tmN1ulvgKaCU2f/bPRCzg==}
+  /@typescript-eslint/utils@6.7.5(eslint@8.51.0)(typescript@5.2.2):
+    resolution: {integrity: sha512-pfRRrH20thJbzPPlPc4j0UNGvH1PjPlhlCMq4Yx7EGjV7lvEeGX0U6MJYe8+SyFutWgSHsdbJ3BXzZccYggezA==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.51.0)
       '@types/json-schema': 7.0.12
       '@types/semver': 7.5.3
-      '@typescript-eslint/scope-manager': 6.7.3
-      '@typescript-eslint/types': 6.7.3
-      '@typescript-eslint/typescript-estree': 6.7.3(typescript@5.2.2)
-      eslint: 8.50.0
+      '@typescript-eslint/scope-manager': 6.7.5
+      '@typescript-eslint/types': 6.7.5
+      '@typescript-eslint/typescript-estree': 6.7.5(typescript@5.2.2)
+      eslint: 8.51.0
       semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
       - typescript
     dev: true
 
-  /@typescript-eslint/visitor-keys@6.7.3:
-    resolution: {integrity: sha512-HEVXkU9IB+nk9o63CeICMHxFWbHWr3E1mpilIQBe9+7L/lH97rleFLVtYsfnWB+JVMaiFnEaxvknvmIzX+CqVg==}
+  /@typescript-eslint/visitor-keys@6.7.5:
+    resolution: {integrity: sha512-3MaWdDZtLlsexZzDSdQWsFQ9l9nL8B80Z4fImSpyllFC/KLqWQRdEcB+gGGO+N3Q2uL40EsG66wZLsohPxNXvg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.7.3
+      '@typescript-eslint/types': 6.7.5
       eslint-visitor-keys: 3.4.3
     dev: true
 
-  /@vitejs/plugin-react@3.1.0(vite@4.4.9):
+  /@vitejs/plugin-react@3.1.0(vite@4.4.11):
     resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -8154,23 +8170,23 @@ packages:
       '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.22.11)
       magic-string: 0.27.0
       react-refresh: 0.14.0
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.3.4(vite@4.4.9)(vue@3.3.4):
-    resolution: {integrity: sha512-ciXNIHKPriERBisHFBvnTbfKa6r9SAesOYXeGDzgegcvy9Q4xdScSHAmKbNT0M3O0S9LKhIf5/G+UYG4NnnzYw==}
+  /@vitejs/plugin-vue@4.4.0(vite@4.4.11)(vue@3.3.4):
+    resolution: {integrity: sha512-xdguqb+VUwiRpSg+nsc2HtbAUSGak25DXYvpQQi4RVU1Xq1uworyoH/md9Rfd8zMmPR/pSghr309QNcftUVseg==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
       vue: 3.3.4
 
-  /@vitest/coverage-v8@0.34.5(vitest@0.34.5):
-    resolution: {integrity: sha512-97xjhRTSdmeeHCm2nNHhT3hLsMYkAhHXm/rwj6SZ3voka8xiCJrwgtfIjoZIFEL4OO0KezGmVuHWQXcMunULIA==}
+  /@vitest/coverage-v8@0.34.6(vitest@0.34.6):
+    resolution: {integrity: sha512-fivy/OK2d/EsJFoEoxHFEnNGTg+MmdZBAVK9Ka4qhXR2K3J0DS08vcGVwzDtXSuUMabLv4KtPcpSKkcMXFDViw==}
     peerDependencies:
       vitest: '>=0.32.0 <1'
     dependencies:
@@ -8185,68 +8201,68 @@ packages:
       std-env: 3.3.3
       test-exclude: 6.0.0
       v8-to-istanbul: 9.1.0
-      vitest: 0.34.5(happy-dom@10.0.3)(sass@1.68.0)(terser@5.20.0)
+      vitest: 0.34.6(happy-dom@10.0.3)(sass@1.69.1)(terser@5.21.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitest/expect@0.34.5:
-    resolution: {integrity: sha512-/3RBIV9XEH+nRpRMqDJBufKIOQaYUH2X6bt0rKSCW0MfKhXFLYsR5ivHifeajRSTsln0FwJbitxLKHSQz/Xwkw==}
+  /@vitest/expect@0.34.6:
+    resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==}
     dependencies:
-      '@vitest/spy': 0.34.5
-      '@vitest/utils': 0.34.5
-      chai: 4.3.7
+      '@vitest/spy': 0.34.6
+      '@vitest/utils': 0.34.6
+      chai: 4.3.10
     dev: true
 
-  /@vitest/runner@0.34.5:
-    resolution: {integrity: sha512-RDEE3ViVvl7jFSCbnBRyYuu23XxmvRTSZWW6W4M7eC5dOsK75d5LIf6uhE5Fqf809DQ1+9ICZZNxhIolWHU4og==}
+  /@vitest/runner@0.34.6:
+    resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==}
     dependencies:
-      '@vitest/utils': 0.34.5
+      '@vitest/utils': 0.34.6
       p-limit: 4.0.0
       pathe: 1.1.1
     dev: true
 
-  /@vitest/snapshot@0.34.5:
-    resolution: {integrity: sha512-+ikwSbhu6z2yOdtKmk/aeoDZ9QPm2g/ZO5rXT58RR9Vmu/kB2MamyDSx77dctqdZfP3Diqv4mbc/yw2kPT8rmA==}
+  /@vitest/snapshot@0.34.6:
+    resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==}
     dependencies:
       magic-string: 0.30.3
       pathe: 1.1.1
       pretty-format: 29.7.0
     dev: true
 
-  /@vitest/spy@0.34.5:
-    resolution: {integrity: sha512-epsicsfhvBjRjCMOC/3k00mP/TBGQy8/P0DxOFiWyLt55gnZ99dqCfCiAsKO17BWVjn4eZRIjKvcqNmSz8gvmg==}
+  /@vitest/spy@0.34.6:
+    resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==}
     dependencies:
       tinyspy: 2.1.1
     dev: true
 
-  /@vitest/utils@0.34.5:
-    resolution: {integrity: sha512-ur6CmmYQoeHMwmGb0v+qwkwN3yopZuZyf4xt1DBBSGBed8Hf9Gmbm/5dEWqgpLPdRx6Av6jcWXrjcKfkTzg/pw==}
+  /@vitest/utils@0.34.6:
+    resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==}
     dependencies:
       diff-sequences: 29.6.3
       loupe: 2.3.6
       pretty-format: 29.7.0
     dev: true
 
-  /@volar/language-core@1.10.0:
-    resolution: {integrity: sha512-ddyWwSYqcbEZNFHm+Z3NZd6M7Ihjcwl/9B5cZd8kECdimVXUFdFi60XHWD27nrWtUQIsUYIG7Ca1WBwV2u2LSQ==}
+  /@volar/language-core@1.10.3:
+    resolution: {integrity: sha512-7Qgwu9bWUHN+cLrOkCbIVBkL+RVPREhvY07wY89dGxi4mY9mQCsUVRRp64F61lX7Nc27meMnvy0sWlzY0x6oQQ==}
     dependencies:
-      '@volar/source-map': 1.10.0
+      '@volar/source-map': 1.10.3
     dev: true
 
-  /@volar/source-map@1.10.0:
-    resolution: {integrity: sha512-/ibWdcOzDGiq/GM1JU2eX8fH1bvAhl66hfe8yEgLEzg9txgr6qb5sQ/DEz5PcDL75tF5H5sCRRwn8Eu8ezi9mw==}
+  /@volar/source-map@1.10.3:
+    resolution: {integrity: sha512-QE9nwK3xsdBQGongHnC9SCR0itx7xUKQFsUDn5HbZY3pHpyXxdY1hSBG0eh9mE+aTKoM4KlqMvrb+19Tv9vS1Q==}
     dependencies:
       muggle-string: 0.3.1
     dev: true
 
-  /@volar/typescript@1.10.0:
-    resolution: {integrity: sha512-OtqGtFbUKYC0pLNIk3mHQp5xWnvL1CJIUc9VE39VdZ/oqpoBh5jKfb9uJ45Y4/oP/WYTrif/Uxl1k8VTPz66Gg==}
+  /@volar/typescript@1.10.3:
+    resolution: {integrity: sha512-n0ar6xGYpRoSvgGMetm/JXP0QAXx+NOUvxCaWCfCjiFivQRSLJeydYDijhoGBUl5KSKosqq9In5L3e/m2TqTcQ==}
     dependencies:
-      '@volar/language-core': 1.10.0
+      '@volar/language-core': 1.10.3
     dev: true
 
-  /@vue-macros/common@1.8.0(rollup@3.29.4)(vue@3.3.4):
+  /@vue-macros/common@1.8.0(rollup@4.0.2)(vue@3.3.4):
     resolution: {integrity: sha512-auDJJzE0z3uRe3867e0DsqcseKImktNf5ojCZgUKqiVxb2yTlwlgOVAYCgoep9oITqxkXQymSvFeKhedi8PhaA==}
     engines: {node: '>=16.14.0'}
     peerDependencies:
@@ -8256,9 +8272,9 @@ packages:
         optional: true
     dependencies:
       '@babel/types': 7.22.17
-      '@rollup/pluginutils': 5.0.4(rollup@3.29.4)
+      '@rollup/pluginutils': 5.0.5(rollup@4.0.2)
       '@vue/compiler-sfc': 3.3.4
-      ast-kit: 0.11.2(rollup@3.29.4)
+      ast-kit: 0.11.2(rollup@4.0.2)
       local-pkg: 0.4.3
       magic-string-ast: 0.3.0
       vue: 3.3.4
@@ -8266,14 +8282,14 @@ packages:
       - rollup
     dev: false
 
-  /@vue-macros/reactivity-transform@0.3.23(rollup@3.29.4)(vue@3.3.4):
+  /@vue-macros/reactivity-transform@0.3.23(rollup@4.0.2)(vue@3.3.4):
     resolution: {integrity: sha512-SubIg1GsNpQdIDJusrcA2FWBgwSY+4jmL0j6SJ6PU85r3rlS+uDhn6AUkqxeZRAdmJnrbGHXDyWUdygOZmWrSg==}
     engines: {node: '>=16.14.0'}
     peerDependencies:
       vue: ^2.7.0 || ^3.2.25
     dependencies:
       '@babel/parser': 7.22.16
-      '@vue-macros/common': 1.8.0(rollup@3.29.4)(vue@3.3.4)
+      '@vue-macros/common': 1.8.0(rollup@4.0.2)(vue@3.3.4)
       '@vue/compiler-core': 3.3.4
       '@vue/shared': 3.3.4
       magic-string: 0.30.3
@@ -8317,20 +8333,20 @@ packages:
       '@vue/compiler-dom': 3.3.4
       '@vue/shared': 3.3.4
 
-  /@vue/language-core@1.8.15(typescript@5.2.2):
-    resolution: {integrity: sha512-zche5Aw8kkvp3YaghuLiOZyVIpoWHjSQ0EfjxGSsqHOPMamdCoa9x3HtbenpR38UMUoKJ88wiWuiOrV3B/Yq+A==}
+  /@vue/language-core@1.8.18(typescript@5.2.2):
+    resolution: {integrity: sha512-byTi+mwSL7XnVRtfWE3MJy3HQryoVSQ3lymauXviegn3G1wwwlSOUljzQe3w5PyesOnBEIxYoavfKzMJnExrBA==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@volar/language-core': 1.10.0
-      '@volar/source-map': 1.10.0
+      '@volar/language-core': 1.10.3
+      '@volar/source-map': 1.10.3
       '@vue/compiler-dom': 3.3.4
       '@vue/reactivity': 3.3.4
       '@vue/shared': 3.3.4
-      minimatch: 9.0.2
+      minimatch: 9.0.3
       muggle-string: 0.3.1
       typescript: 5.2.2
       vue-template-compiler: 2.7.14
@@ -8387,11 +8403,11 @@ packages:
       '@vue/server-renderer': 3.3.4(vue@3.3.4)
     dev: true
 
-  /@vue/typescript@1.8.15(typescript@5.2.2):
-    resolution: {integrity: sha512-qWyanQKXOsK84S8rP7QBrqsvUdQ0nZABZmTjXMpb3ox4Bp5IbkscREA3OPUrkgl64mAxwwCzIWcOc3BPTCPjQw==}
+  /@vue/typescript@1.8.18(typescript@5.2.2):
+    resolution: {integrity: sha512-3M+lu+DUwJW0fNwd/rLE0FenmELxcC6zxgm/YZ25jSTi+uNGj9L5XvXvf20guC69gQvZ+cg49tTxbepfFVuNNQ==}
     dependencies:
-      '@volar/typescript': 1.10.0
-      '@vue/language-core': 1.8.15(typescript@5.2.2)
+      '@volar/typescript': 1.10.3
+      '@vue/language-core': 1.8.18(typescript@5.2.2)
     transitivePeerDependencies:
       - typescript
     dev: true
@@ -8848,12 +8864,12 @@ packages:
     resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
     dev: true
 
-  /ast-kit@0.11.2(rollup@3.29.4):
+  /ast-kit@0.11.2(rollup@4.0.2):
     resolution: {integrity: sha512-Q0DjXK4ApbVoIf9GLyCo252tUH44iTnD/hiJ2TQaJeydYWSpKk0sI34+WMel8S9Wt5pbLgG02oJ+gkgX5DV3sQ==}
     engines: {node: '>=16.14.0'}
     dependencies:
       '@babel/parser': 7.22.16
-      '@rollup/pluginutils': 5.0.4(rollup@3.29.4)
+      '@rollup/pluginutils': 5.0.5(rollup@4.0.2)
       pathe: 1.1.1
     transitivePeerDependencies:
       - rollup
@@ -9317,8 +9333,8 @@ packages:
     dependencies:
       node-gyp-build: 4.6.0
 
-  /bullmq@4.11.4:
-    resolution: {integrity: sha512-LuCR3ILngYa3CLC5jyf8DU4Yokj9T12MWwBogP3S4IiJUtbJsQ9GTGFxho3imRxXfcd9DUfrABT/pSoqVigXiQ==}
+  /bullmq@4.12.3:
+    resolution: {integrity: sha512-4uPp4NQTALFF+eFK7g8VJM+rt0aiduQdzBomgiEO1OK4OE+TdgC6cjGXooKI/asuB8iDhSZ+pSnGYy5Xyr6qRA==}
     dependencies:
       cron-parser: 4.8.1
       glob: 8.1.0
@@ -9514,14 +9530,14 @@ packages:
     dependencies:
       nofilter: 3.1.0
 
-  /chai@4.3.7:
-    resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==}
+  /chai@4.3.10:
+    resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==}
     engines: {node: '>=4'}
     dependencies:
       assertion-error: 1.1.0
-      check-error: 1.0.2
+      check-error: 1.0.3
       deep-eql: 4.1.3
-      get-func-name: 2.0.0
+      get-func-name: 2.0.2
       loupe: 2.3.6
       pathval: 1.1.1
       type-detect: 4.0.8
@@ -9630,8 +9646,10 @@ packages:
       hammerjs: 2.0.8
     dev: false
 
-  /check-error@1.0.2:
-    resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==}
+  /check-error@1.0.3:
+    resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==}
+    dependencies:
+      get-func-name: 2.0.2
     dev: true
 
   /check-more-types@2.24.0:
@@ -9683,8 +9701,8 @@ packages:
     engines: {node: '>=10'}
     requiresBuild: true
 
-  /chromatic@7.2.0:
-    resolution: {integrity: sha512-EbuvmsM6XAVFC4EQpqR2AT2PaXY4IS8qWxxg6N10AhpRulfX2b2AtW1hUc88cCosRyztd6esxkBdj3FSKR7zVw==}
+  /chromatic@7.2.3:
+    resolution: {integrity: sha512-UEcHB1nkPoHWoRybPzv6BOVqPr7PqDNuz3u8NCRg7KJciouOb20HjiUQx4Dh9mgA7JUsb2WeGHE2SG/0fHH0PA==}
     hasBin: true
     dev: false
 
@@ -10021,7 +10039,7 @@ packages:
       readable-stream: 3.6.0
     dev: false
 
-  /create-jest@29.7.0(@types/node@20.7.1):
+  /create-jest@29.7.0(@types/node@20.8.4):
     resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -10030,7 +10048,7 @@ packages:
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.11
-      jest-config: 29.7.0(@types/node@20.7.1)
+      jest-config: 29.7.0(@types/node@20.8.4)
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -11045,7 +11063,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.7)(eslint@8.50.0):
+  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.7)(eslint@8.51.0):
     resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -11066,15 +11084,15 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+      '@typescript-eslint/parser': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       debug: 3.2.7(supports-color@5.5.0)
-      eslint: 8.50.0
+      eslint: 8.51.0
       eslint-import-resolver-node: 0.3.7
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.3)(eslint@8.50.0):
+  /eslint-plugin-import@2.28.1(@typescript-eslint/parser@6.7.5)(eslint@8.51.0):
     resolution: {integrity: sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -11084,16 +11102,16 @@ packages:
       '@typescript-eslint/parser':
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 6.7.3(eslint@8.50.0)(typescript@5.2.2)
+      '@typescript-eslint/parser': 6.7.5(eslint@8.51.0)(typescript@5.2.2)
       array-includes: 3.1.6
       array.prototype.findlastindex: 1.2.2
       array.prototype.flat: 1.3.1
       array.prototype.flatmap: 1.3.1
       debug: 3.2.7(supports-color@5.5.0)
       doctrine: 2.1.0
-      eslint: 8.50.0
+      eslint: 8.51.0
       eslint-import-resolver-node: 0.3.7
-      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.3)(eslint-import-resolver-node@0.3.7)(eslint@8.50.0)
+      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.7.5)(eslint-import-resolver-node@0.3.7)(eslint@8.51.0)
       has: 1.0.3
       is-core-module: 2.13.0
       is-glob: 4.0.3
@@ -11109,19 +11127,19 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-vue@9.17.0(eslint@8.50.0):
+  /eslint-plugin-vue@9.17.0(eslint@8.51.0):
     resolution: {integrity: sha512-r7Bp79pxQk9I5XDP0k2dpUC7Ots3OSWgvGZNu3BxmKK6Zg7NgVtcOB6OCna5Kb9oQwJPl5hq183WD0SY5tZtIQ==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0)
-      eslint: 8.50.0
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.51.0)
+      eslint: 8.51.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
       postcss-selector-parser: 6.0.13
       semver: 7.5.4
-      vue-eslint-parser: 9.3.1(eslint@8.50.0)
+      vue-eslint-parser: 9.3.2(eslint@8.51.0)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -11131,14 +11149,6 @@ packages:
     resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==}
     dev: true
 
-  /eslint-scope@7.2.0:
-    resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    dependencies:
-      esrecurse: 4.3.0
-      estraverse: 5.3.0
-    dev: true
-
   /eslint-scope@7.2.2:
     resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -11147,25 +11157,20 @@ packages:
       estraverse: 5.3.0
     dev: true
 
-  /eslint-visitor-keys@3.4.1:
-    resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    dev: true
-
   /eslint-visitor-keys@3.4.3:
     resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /eslint@8.50.0:
-    resolution: {integrity: sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==}
+  /eslint@8.51.0:
+    resolution: {integrity: sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.51.0)
       '@eslint-community/regexpp': 4.6.2
       '@eslint/eslintrc': 2.1.2
-      '@eslint/js': 8.50.0
+      '@eslint/js': 8.51.0
       '@humanwhocodes/config-array': 0.11.11
       '@humanwhocodes/module-importer': 1.0.1
       '@nodelib/fs.walk': 1.2.8
@@ -11203,15 +11208,6 @@ packages:
       - supports-color
     dev: true
 
-  /espree@9.5.2:
-    resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    dependencies:
-      acorn: 8.10.0
-      acorn-jsx: 5.3.2(acorn@8.10.0)
-      eslint-visitor-keys: 3.4.3
-    dev: true
-
   /espree@9.6.1:
     resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -12000,8 +11996,8 @@ packages:
     resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
     engines: {node: 6.* || 8.* || >= 10.*}
 
-  /get-func-name@2.0.0:
-    resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==}
+  /get-func-name@2.0.2:
+    resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
     dev: true
 
   /get-intrinsic@1.2.0:
@@ -12287,8 +12283,8 @@ packages:
     resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
     dev: true
 
-  /graphql@16.6.0:
-    resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
+  /graphql@16.8.1:
+    resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==}
     engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
     dev: true
 
@@ -13250,7 +13246,7 @@ packages:
       '@jest/expect': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       chalk: 4.1.2
       co: 4.6.0
       dedent: 1.3.0
@@ -13271,7 +13267,7 @@ packages:
       - supports-color
     dev: true
 
-  /jest-cli@29.7.0(@types/node@20.7.1):
+  /jest-cli@29.7.0(@types/node@20.8.4):
     resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13285,10 +13281,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.7.1)
+      create-jest: 29.7.0(@types/node@20.8.4)
       exit: 0.1.2
       import-local: 3.1.0
-      jest-config: 29.7.0(@types/node@20.7.1)
+      jest-config: 29.7.0(@types/node@20.8.4)
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.6.2
@@ -13299,7 +13295,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest-config@29.7.0(@types/node@20.7.1):
+  /jest-config@29.7.0(@types/node@20.8.4):
     resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -13314,7 +13310,7 @@ packages:
       '@babel/core': 7.22.11
       '@jest/test-sequencer': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       babel-jest: 29.7.0(@babel/core@7.22.11)
       chalk: 4.1.2
       ci-info: 3.7.1
@@ -13394,7 +13390,7 @@ packages:
       '@jest/environment': 29.7.0
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       jest-mock: 29.7.0
       jest-util: 29.7.0
     dev: true
@@ -13424,7 +13420,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.6
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -13485,7 +13481,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.7.1
+      '@types/node': 20.8.4
     dev: true
 
   /jest-mock@29.7.0:
@@ -13493,7 +13489,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       jest-util: 29.7.0
     dev: true
 
@@ -13548,7 +13544,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -13579,7 +13575,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       chalk: 4.1.2
       cjs-module-lexer: 1.2.2
       collect-v8-coverage: 1.0.1
@@ -13631,7 +13627,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       chalk: 4.1.2
       ci-info: 3.7.1
       graceful-fs: 4.2.11
@@ -13656,7 +13652,7 @@ packages:
     dependencies:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -13675,13 +13671,13 @@ packages:
     resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
 
-  /jest@29.7.0(@types/node@20.7.1):
+  /jest@29.7.0(@types/node@20.8.4):
     resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13694,7 +13690,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.7.1)
+      jest-cli: 29.7.0(@types/node@20.8.4)
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros
@@ -13957,8 +13953,8 @@ packages:
     resolution: {integrity: sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==}
     dev: false
 
-  /jssha@3.3.0:
-    resolution: {integrity: sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==}
+  /jssha@3.3.1:
+    resolution: {integrity: sha512-VCMZj12FCFMQYcFLPRm/0lOBbLi8uM2BhXPTqw3U4YAfs4AZfiApOoBLoN8cQE60Z50m1MYMTQVCfgF/KaCVhQ==}
     dev: false
 
   /jstransformer@1.0.0:
@@ -14188,7 +14184,7 @@ packages:
   /loupe@2.3.6:
     resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
     dependencies:
-      get-func-name: 2.0.0
+      get-func-name: 2.0.2
     dev: true
 
   /lowercase-keys@2.0.0:
@@ -14521,6 +14517,13 @@ packages:
     dependencies:
       brace-expansion: 2.0.1
 
+  /minimatch@9.0.3:
+    resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+    engines: {node: '>=16 || 14 >=14.17'}
+    dependencies:
+      brace-expansion: 2.0.1
+    dev: true
+
   /minimist-options@4.1.0:
     resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
     engines: {node: '>= 6'}
@@ -14664,10 +14667,6 @@ packages:
   /ms@2.0.0:
     resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
 
-  /ms@2.1.1:
-    resolution: {integrity: sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==}
-    dev: true
-
   /ms@2.1.2:
     resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
 
@@ -14701,17 +14700,17 @@ packages:
       msgpackr-extract: 3.0.2
     dev: false
 
-  /msw-storybook-addon@1.8.0(msw@1.3.1):
+  /msw-storybook-addon@1.8.0(msw@1.3.2):
     resolution: {integrity: sha512-dw3vZwqjixmiur0vouRSOax7wPSu9Og2Hspy9JZFHf49bZRjwDiLF0Pfn2NXEkGviYJOJiGxS1ejoTiUwoSg4A==}
     peerDependencies:
       msw: '>=0.35.0 <2.0.0'
     dependencies:
       is-node-process: 1.0.1
-      msw: 1.3.1(typescript@5.2.2)
+      msw: 1.3.2(typescript@5.2.2)
     dev: true
 
-  /msw@1.3.1(typescript@5.2.2):
-    resolution: {integrity: sha512-GhP5lHSTXNlZb9EaKgPRJ01YAnVXwzkvnTzRn4W8fxU2DXuJrRO+Nb6OHdYqB4fCkwSNpIJH9JkON5Y6rHqJMQ==}
+  /msw@1.3.2(typescript@5.2.2):
+    resolution: {integrity: sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==}
     engines: {node: '>=14'}
     hasBin: true
     requiresBuild: true
@@ -14729,7 +14728,7 @@ packages:
       chalk: 4.1.2
       chokidar: 3.5.3
       cookie: 0.4.2
-      graphql: 16.6.0
+      graphql: 16.8.1
       headers-polyfill: 3.2.5
       inquirer: 8.2.5
       is-node-process: 1.2.0
@@ -14983,8 +14982,8 @@ packages:
   /node-releases@2.0.13:
     resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
 
-  /nodemailer@6.9.5:
-    resolution: {integrity: sha512-/dmdWo62XjumuLc5+AYQZeiRj+PRR8y8qKtFCOyuOl1k/hckZd8durUUHs/ucKx6/8kN+wFxqKJlQ/LK/qR5FA==}
+  /nodemailer@6.9.6:
+    resolution: {integrity: sha512-s7pDtWwe5fLMkQUhw8TkWB/wnZ7SRdd9HRZslq/s24hlZvBP3j32N/ETLmnqTpmj4xoBZL9fOWyCIZ7r2HORHg==}
     engines: {node: '>=6.0.0'}
     dev: false
 
@@ -15320,10 +15319,10 @@ packages:
     resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
     dev: true
 
-  /otpauth@9.1.4:
-    resolution: {integrity: sha512-T6T0E1WlzwKWESq8K0Ja47u01XjmDmRY/AiUoMAc6xZI/OsTsD4cqBrfpt2WfJ29W5pRiWkuUuyHdNQl0/Ic+Q==}
+  /otpauth@9.1.5:
+    resolution: {integrity: sha512-mnic91MZxvj04Ir7FN8Xi6wF3FU8D+s6M5p6FQaSS91/csKswoOI9Dk7kKSnGFAoBYgGTTO+OWScV0nJuzrbPg==}
     dependencies:
-      jssha: 3.3.0
+      jssha: 3.3.1
     dev: false
 
   /outvariant@1.4.0:
@@ -15653,8 +15652,8 @@ packages:
       split2: 4.1.0
     dev: false
 
-  /photoswipe@5.4.1:
-    resolution: {integrity: sha512-iauO0fP4oMdZvjlXzeIe8um1fZatkGE0bqdoIwpb65jlo/KK1KhfD7Z51+0YhS2tC4FOoOtE1p0c4o/HbY1s2Q==}
+  /photoswipe@5.4.2:
+    resolution: {integrity: sha512-z5hr36nAIPOZbHJPbCJ/mQ3+ZlizttF9za5gKXKH/us1k4KNHaRbC63K1Px5sVVKUtGb/2+ixHpKqtwl0WAwvA==}
     engines: {node: '>= 0.12.0'}
     dev: false
 
@@ -17160,6 +17159,25 @@ packages:
     optionalDependencies:
       fsevents: 2.3.2
 
+  /rollup@4.0.2:
+    resolution: {integrity: sha512-MCScu4usMPCeVFaiLcgMDaBQeYi1z6vpWxz0r0hq0Hv77Y2YuOTZldkuNJ54BdYBH3e+nkrk6j0Rre/NLDBYzg==}
+    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
+    hasBin: true
+    optionalDependencies:
+      '@rollup/rollup-android-arm-eabi': 4.0.2
+      '@rollup/rollup-android-arm64': 4.0.2
+      '@rollup/rollup-darwin-arm64': 4.0.2
+      '@rollup/rollup-darwin-x64': 4.0.2
+      '@rollup/rollup-linux-arm-gnueabihf': 4.0.2
+      '@rollup/rollup-linux-arm64-gnu': 4.0.2
+      '@rollup/rollup-linux-arm64-musl': 4.0.2
+      '@rollup/rollup-linux-x64-gnu': 4.0.2
+      '@rollup/rollup-linux-x64-musl': 4.0.2
+      '@rollup/rollup-win32-arm64-msvc': 4.0.2
+      '@rollup/rollup-win32-ia32-msvc': 4.0.2
+      '@rollup/rollup-win32-x64-msvc': 4.0.2
+      fsevents: 2.3.2
+
   /rrweb-cssom@0.6.0:
     resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
     dev: false
@@ -17196,10 +17214,6 @@ packages:
       isarray: 2.0.5
     dev: true
 
-  /safe-buffer@5.1.1:
-    resolution: {integrity: sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==}
-    dev: true
-
   /safe-buffer@5.1.2:
     resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
 
@@ -17239,8 +17253,8 @@ packages:
       postcss: 8.4.31
     dev: false
 
-  /sass@1.68.0:
-    resolution: {integrity: sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==}
+  /sass@1.69.1:
+    resolution: {integrity: sha512-nc969GvTVz38oqKgYYVHM/Iq7Yl33IILy5uqaH2CWSiSUmRCvw+UR7tA3845Sp4BD5ykCUimvrT3k1EjTwpVUA==}
     engines: {node: '>=14.0.0'}
     hasBin: true
     dependencies:
@@ -17321,17 +17335,6 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
-  /serve-favicon@2.5.0:
-    resolution: {integrity: sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA==}
-    engines: {node: '>= 0.8.0'}
-    dependencies:
-      etag: 1.8.1
-      fresh: 0.5.2
-      ms: 2.1.1
-      parseurl: 1.3.3
-      safe-buffer: 5.1.1
-    dev: true
-
   /serve-static@1.15.0:
     resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==}
     engines: {node: '>= 0.8.0'}
@@ -17846,11 +17849,11 @@ packages:
     resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==}
     dev: true
 
-  /storybook@7.4.5:
-    resolution: {integrity: sha512-J7fidphTJ6SJHlR8f/USQE30K6ipbynLVLsTOz0bNYW/0Ua2t9u6dAYGbbq6bLikl3zxzQbdm9lXMUzmaYAdIA==}
+  /storybook@7.4.6:
+    resolution: {integrity: sha512-YkFSpnR47j5zz7yElA+2axLjXN7K7TxDGJRHHlqXmG5iQ0PXzmjrj2RxMDKFz4Ybp/QjEUoJ4rx//ESEY0Nb5A==}
     hasBin: true
     dependencies:
-      '@storybook/cli': 7.4.5
+      '@storybook/cli': 7.4.6
     transitivePeerDependencies:
       - bufferutil
       - encoding
@@ -18122,8 +18125,8 @@ packages:
     resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==}
     dev: true
 
-  /systeminformation@5.21.9:
-    resolution: {integrity: sha512-7pI4mu9P/2MGDV0T49B52E7IULBGj+kRVk6JSYUj5qfAk7N7C7aNX15fXziqrbgZntc6/jjYzWeb/x41jhg/eA==}
+  /systeminformation@5.21.11:
+    resolution: {integrity: sha512-dIJEGoP5W7k4JJGje/b+inJrOL5hV9LPsUi5ndBvJydI80CVEcu2DZYgt6prdRErDi2SA4SqYd/WMR4b+u34mA==}
     engines: {node: '>=8.0.0'}
     os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
     hasBin: true
@@ -18216,8 +18219,8 @@ packages:
       unique-string: 2.0.0
     dev: true
 
-  /terser@5.20.0:
-    resolution: {integrity: sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==}
+  /terser@5.21.0:
+    resolution: {integrity: sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==}
     engines: {node: '>=10'}
     hasBin: true
     dependencies:
@@ -18751,6 +18754,9 @@ packages:
     resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
     dev: true
 
+  /undici-types@5.25.3:
+    resolution: {integrity: sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==}
+
   /undici@5.22.1:
     resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==}
     engines: {node: '>=14.0'}
@@ -19020,8 +19026,8 @@ packages:
       core-util-is: 1.0.2
       extsprintf: 1.3.0
 
-  /vite-node@0.34.5(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0):
-    resolution: {integrity: sha512-RNZ+DwbCvDoI5CbCSQSyRyzDTfFvFauvMs6Yq4ObJROKlIKuat1KgSX/Ako5rlDMfVCyMcpMRMTkJBxd6z8YRA==}
+  /vite-node@0.34.6(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0):
+    resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
     dependencies:
@@ -19030,7 +19036,7 @@ packages:
       mlly: 1.4.0
       pathe: 1.1.1
       picocolors: 1.0.0
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19046,8 +19052,8 @@ packages:
     resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
     dev: true
 
-  /vite@4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0):
-    resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==}
+  /vite@4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0):
+    resolution: {integrity: sha512-ksNZJlkcU9b0lBwAGZGGaZHCMqHsc8OpgtoYhsQ4/I2v5cnpmmmqe5pM4nv/4Hn6G/2GhTdj0DhZh2e+Er1q5A==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
@@ -19074,29 +19080,29 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.7.1
+      '@types/node': 20.8.4
       esbuild: 0.18.17
       postcss: 8.4.31
       rollup: 3.29.4
-      sass: 1.68.0
-      terser: 5.20.0
+      sass: 1.69.1
+      terser: 5.21.0
     optionalDependencies:
       fsevents: 2.3.2
 
-  /vitest-fetch-mock@0.2.2(vitest@0.34.5):
+  /vitest-fetch-mock@0.2.2(vitest@0.34.6):
     resolution: {integrity: sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==}
     engines: {node: '>=14.14.0'}
     peerDependencies:
       vitest: '>=0.16.0'
     dependencies:
       cross-fetch: 3.1.5
-      vitest: 0.34.5(happy-dom@10.0.3)(sass@1.68.0)(terser@5.20.0)
+      vitest: 0.34.6(happy-dom@10.0.3)(sass@1.69.1)(terser@5.21.0)
     transitivePeerDependencies:
       - encoding
     dev: true
 
-  /vitest@0.34.5(happy-dom@10.0.3)(sass@1.68.0)(terser@5.20.0):
-    resolution: {integrity: sha512-CPI68mmnr2DThSB3frSuE5RLm9wo5wU4fbDrDwWQQB1CWgq9jQVoQwnQSzYAjdoBOPoH2UtXpOgHVge/uScfZg==}
+  /vitest@0.34.6(happy-dom@10.0.3)(sass@1.69.1)(terser@5.21.0):
+    resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
     peerDependencies:
@@ -19128,16 +19134,16 @@ packages:
     dependencies:
       '@types/chai': 4.3.5
       '@types/chai-subset': 1.3.3
-      '@types/node': 20.7.1
-      '@vitest/expect': 0.34.5
-      '@vitest/runner': 0.34.5
-      '@vitest/snapshot': 0.34.5
-      '@vitest/spy': 0.34.5
-      '@vitest/utils': 0.34.5
+      '@types/node': 20.8.4
+      '@vitest/expect': 0.34.6
+      '@vitest/runner': 0.34.6
+      '@vitest/snapshot': 0.34.6
+      '@vitest/spy': 0.34.6
+      '@vitest/utils': 0.34.6
       acorn: 8.10.0
       acorn-walk: 8.2.0
       cac: 6.7.14
-      chai: 4.3.7
+      chai: 4.3.10
       debug: 4.3.4(supports-color@8.1.1)
       happy-dom: 10.0.3
       local-pkg: 0.4.3
@@ -19148,8 +19154,8 @@ packages:
       strip-literal: 1.0.1
       tinybench: 2.5.0
       tinypool: 0.7.0
-      vite: 4.4.9(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
-      vite-node: 0.34.5(@types/node@20.7.1)(sass@1.68.0)(terser@5.20.0)
+      vite: 4.4.11(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
+      vite-node: 0.34.6(@types/node@20.8.4)(sass@1.69.1)(terser@5.21.0)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -19165,8 +19171,8 @@ packages:
     resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
     engines: {node: '>=0.10.0'}
 
-  /vue-component-type-helpers@1.8.15:
-    resolution: {integrity: sha512-RKiPRKW4BdwgmQ9vaNkHYKAThdTbgU4TOphVyyzqxRwsOJOoRIrb+vB49XLvs5CKPNrvxMXZMwPe5FyJCqFWyg==}
+  /vue-component-type-helpers@1.8.18:
+    resolution: {integrity: sha512-SklLIg782E5Ff0qdE68AUrRBhT2YGW97edBewNEjCWCw+RSETcGOjA8m1/6T68CXkymWBSk+KDpPXqIGthqCDg==}
     dev: true
 
   /vue-demi@0.13.11(vue@3.3.4):
@@ -19202,17 +19208,17 @@ packages:
       - vue
     dev: true
 
-  /vue-eslint-parser@9.3.1(eslint@8.50.0):
-    resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
+  /vue-eslint-parser@9.3.2(eslint@8.51.0):
+    resolution: {integrity: sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.50.0
-      eslint-scope: 7.2.0
-      eslint-visitor-keys: 3.4.1
-      espree: 9.5.2
+      eslint: 8.51.0
+      eslint-scope: 7.2.2
+      eslint-visitor-keys: 3.4.3
+      espree: 9.6.1
       esquery: 1.4.2
       lodash: 4.17.21
       semver: 7.5.4
@@ -19244,14 +19250,14 @@ packages:
       he: 1.2.0
     dev: true
 
-  /vue-tsc@1.8.15(typescript@5.2.2):
-    resolution: {integrity: sha512-4DoB3LUj7IToLmggoCxRiFG+QU5lem0nv03m1ocqugXA9rSVoTOEoYYaP8vu8b99Eh+/cCVdYOeIAQ+RsgUYUw==}
+  /vue-tsc@1.8.18(typescript@5.2.2):
+    resolution: {integrity: sha512-AwQxBB9SZX308TLL1932P1JByuMsXC2jLfRBGt8SBdm1e3cXkDlFaXUAqibfKnoQ1ZC2zO2NSbeBNdSjOcdvJw==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
-      '@vue/language-core': 1.8.15(typescript@5.2.2)
-      '@vue/typescript': 1.8.15(typescript@5.2.2)
+      '@vue/language-core': 1.8.18(typescript@5.2.2)
+      '@vue/typescript': 1.8.18(typescript@5.2.2)
       semver: 7.5.4
       typescript: 5.2.2
     dev: true
@@ -19707,7 +19713,7 @@ packages:
       sharp: 0.31.3
     dev: false
 
-  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.4.5)(@storybook/components@7.4.4)(@storybook/core-events@7.4.5)(@storybook/manager-api@7.4.5)(@storybook/preview-api@7.4.5)(@storybook/theming@7.4.5)(@storybook/types@7.4.5)(react-dom@18.2.0)(react@18.2.0):
+  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.4.6)(@storybook/components@7.4.6)(@storybook/core-events@7.4.6)(@storybook/manager-api@7.4.6)(@storybook/preview-api@7.4.6)(@storybook/theming@7.4.6)(@storybook/types@7.4.6)(react-dom@18.2.0)(react@18.2.0):
     resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640}
     id: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640
     name: storybook-addon-misskey-theme
@@ -19728,13 +19734,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/components': 7.4.4(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.4.5
-      '@storybook/manager-api': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.4.5
-      '@storybook/theming': 7.4.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.4.5
+      '@storybook/blocks': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/components': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.4.6
+      '@storybook/manager-api': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.4.6
+      '@storybook/theming': 7.4.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.4.6
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true