diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index af0a90dc9558a5a393905fa093e32fe2191b9160..39682e1542cbb20936ed5e7911d1635b933585ef 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -95,15 +95,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#   ┌─────────────────────────────┐
-#───┘ Elasticsearch configuration └─────────────────────────────
-
-#elasticsearch:
-#  host: localhost
-#  port: 9200
-#  ssl: false
-#  user:
-#  pass:
+#   ┌───────────────────────────┐
+#───┘ MeiliSearch configuration └─────────────────────────────
+
+#meilisearch:
+#  host: meilisearch
+#  port: 7700
+#  apiKey: ''
+#  ssl: true
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
diff --git a/.config/example.yml b/.config/example.yml
index 57e2b56b78c52c049e56a3ef28228f53f655a46a..ef8373c4da57f3093d5eb62d813c1b1b9b02924f 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -95,15 +95,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#   ┌─────────────────────────────┐
-#───┘ Elasticsearch configuration └─────────────────────────────
+#   ┌───────────────────────────┐
+#───┘ MeiliSearch configuration └─────────────────────────────
 
-#elasticsearch:
+#meilisearch:
 #  host: localhost
-#  port: 9200
-#  ssl: false
-#  user: 
-#  pass: 
+#  port: 7700
+#  apiKey: ''
+#  ssl: true
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
@@ -133,16 +132,20 @@ id: 'aid'
 #clusterLimit: 1
 
 # Job concurrency per worker
-# deliverJobConcurrency: 128
-# inboxJobConcurrency: 16
+#deliverJobConcurrency: 128
+#inboxJobConcurrency: 16
+#relashionshipJobConcurrency: 16
+# What's relashionshipJob?:
+#  Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
 
 # Job rate limiter
-# deliverJobPerSec: 128
-# inboxJobPerSec: 16
+#deliverJobPerSec: 128
+#inboxJobPerSec: 16
+#relashionshipJobPerSec: 64
 
 # Job attempts
-# deliverJobMaxAttempts: 12
-# inboxJobMaxAttempts: 8
+#deliverJobMaxAttempts: 12
+#inboxJobMaxAttempts: 8
 
 # IP address family used for outgoing request (ipv4, ipv6 or dual)
 #outgoingAddressFamily: ipv4
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 6accd434763e8dea26a18c8bd96d8dcab5976865..a47804ab07a0c6d35ad0c9e98e48ae1040799de3 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -4,7 +4,10 @@
 	"service": "app",
 	"workspaceFolder": "/workspace",
 	"features": {
-		"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
+		"ghcr.io/devcontainers-contrib/features/pnpm:2": {},
+		"ghcr.io/devcontainers/features/node:1": {
+			"version": "18.16.0"
+		}
 	},
 	"forwardPorts": [3000],
 	"postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh",
diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml
index 2af306e3dac8d16c933fafa31c8ecd9d9aadde59..1d761ae75e8449c07475169ac6cc84880eec5d10 100644
--- a/.devcontainer/devcontainer.yml
+++ b/.devcontainer/devcontainer.yml
@@ -95,15 +95,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#   ┌─────────────────────────────┐
-#───┘ Elasticsearch configuration └─────────────────────────────
-
-#elasticsearch:
-#  host: localhost
-#  port: 9200
-#  ssl: false
-#  user:
-#  pass:
+#   ┌───────────────────────────┐
+#───┘ MeiliSearch configuration └─────────────────────────────
+
+#meilisearch:
+#  host: meilisearch
+#  port: 7700
+#  apiKey: ''
+#  ssl: true
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
diff --git a/.dockerignore b/.dockerignore
index 151ede038eb38120bba59957b9c3eac41343c493..1de0c7982bcdacbdd8b03b756a31a5b6a1824fd8 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -8,7 +8,6 @@ build/
 built/
 db/
 docker-compose.yml
-elasticsearch/
 node_modules/
 packages/*/node_modules
 redis/
diff --git a/.github/ISSUE_TEMPLATE/01_bug-report.md b/.github/ISSUE_TEMPLATE/01_bug-report.md
index b4a2f698793dfd6458ce0fa5ecc58405e2d65094..f6fd593c859ddf555a166a93278f150f88c6f60f 100644
--- a/.github/ISSUE_TEMPLATE/01_bug-report.md
+++ b/.github/ISSUE_TEMPLATE/01_bug-report.md
@@ -37,7 +37,10 @@ Please include errors from the developer console and/or server log files if you
 ## 📌 Environment
 
 <!-- Tell us where on the platform it happens -->
+<!-- DO NOT WRITE "latest". Please provide the specific version. -->
 
 Misskey version:
+PostgreSQL version:
+Redis version:
 Your OS:
 Your browser:
diff --git a/.github/labeler.yml b/.github/labeler.yml
index b4fd0dd5dffaf97fa7a1be92f04eb35ad4b4082a..137be487c0269d08518a57c634c995e80d86d441 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -1,12 +1,21 @@
-'⚙️Server':
+'packages/backend':
 - packages/backend/**/*
 
-'🖥️Client':
+'packages/backend:test':
+- packages/backend/test/**/*
+
+'packages/frontend':
 - packages/frontend/**/*
 
-'🧪Test':
+'packages/frontend:test':
 - cypress/**/*
-- packages/backend/test/**/*
 
-'‼️ wrong locales':
-- any: ['locales/*.yml', '!locales/ja-JP.yml']
+'packages/sw':
+- packages/sw/**/*
+
+'packages/misskey-js':
+- packages/misskey-js/**/*
+
+'packages/misskey-js:test':
+- packages/misskey-js/test/**/*
+- packages/misskey-js/test-d/**/*
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 0739fee709200d3cb23e300fc59e9e073ea09fad..e78b82c47ccb681bb28223b2d9153d34e91c1e0f 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -19,5 +19,6 @@ https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
 ## Checklist
 - [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
 - [ ] Test working in a local environment
+- [ ] (If needed) Add story of storybook
 - [ ] (If needed) Update CHANGELOG.md
 - [ ] (If possible) Add tests
diff --git a/.github/workflows/api-misskey-js.yml b/.github/workflows/api-misskey-js.yml
index 6411d63bd5d0c871f4928594e19eeba7cf6f0189..ed004c78dc18739717439e4d28420181928b65fe 100644
--- a/.github/workflows/api-misskey-js.yml
+++ b/.github/workflows/api-misskey-js.yml
@@ -16,7 +16,7 @@ jobs:
       - name: Setup Node.js
         uses: actions/setup-node@v3.6.0
         with:
-          node-version: 18.x
+          node-version-file: '.node-version'
           cache: 'pnpm'
 
       - name: Install dependencies
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 1c6615e17f1a070f8af63a0487b2c5ed8e49e500..0f3702f9582cfc62e791837fa6ee24761577242b 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -17,11 +17,11 @@ jobs:
         submodules: true
     - uses: pnpm/action-setup@v2
       with:
-        version: 7
+        version: 8
         run_install: false
     - uses: actions/setup-node@v3.6.0
       with:
-        node-version: 18.x
+        node-version-file: '.node-version'
         cache: 'pnpm'
     - run: corepack enable
     - run: pnpm i --frozen-lockfile
@@ -48,7 +48,7 @@ jobs:
         run_install: false
     - uses: actions/setup-node@v3.6.0
       with:
-        node-version: 18.x
+        node-version-file: '.node-version'
         cache: 'pnpm'
     - run: corepack enable
     - run: pnpm i --frozen-lockfile
@@ -74,7 +74,7 @@ jobs:
         run_install: false
     - uses: actions/setup-node@v3.6.0
       with:
-        node-version: 18.x
+        node-version-file: '.node-version'
         cache: 'pnpm'
     - run: corepack enable
     - run: pnpm i --frozen-lockfile
diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index 6792674d9fe0058d733b1a9d7e5c9abbe5611492..eb6ace27da0ad1d4a4e003a26b888d978e672bc1 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -2,8 +2,10 @@ name: Storybook
 
 on:
   push:
-    branches-ignore:
-      - l10n_develop
+    branches:
+      - master
+      - develop
+  pull_request_target:
 
 jobs:
   build:
@@ -17,15 +19,18 @@ jobs:
       with:
         fetch-depth: 0
         submodules: true
+    - name: Checkout HEAD
+      if: github.event_name == 'pull_request'
+      run: git checkout ${{ github.head_ref }}
     - name: Install pnpm
       uses: pnpm/action-setup@v2
       with:
-        version: 7
+        version: 8
         run_install: false
     - name: Use Node.js 18.x
       uses: actions/setup-node@v3.6.0
       with:
-        node-version: 18.x
+        node-version-file: '.node-version'
         cache: 'pnpm'
     - run: corepack enable
     - run: pnpm i --frozen-lockfile
@@ -36,13 +41,13 @@ jobs:
     - name: Build storybook
       run: pnpm --filter frontend build-storybook
     - name: Publish to Chromatic
-      if: github.ref == 'refs/heads/master'
+      if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
       run: pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static
       env:
         CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
     - name: Publish to Chromatic
-      if: github.ref != 'refs/heads/master'
-      id: chromatic
+      if: github.event_name != 'pull_request' && github.ref != 'refs/heads/master'
+      id: chromatic_push
       run: |
         DIFF="${{ github.event.before }} HEAD"
         if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
@@ -55,16 +60,31 @@ jobs:
         pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
       env:
         CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
+    - name: Publish to Chromatic
+      if: github.event_name == 'pull_request'
+      id: chromatic_pull_request
+      run: |
+        DIFF="${{ github.base_ref }} HEAD"
+        if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
+          DIFF="HEAD"
+        fi
+        CHROMATIC_PARAMETER="$(node packages/frontend/.storybook/changes.js $(git diff-tree --no-commit-id --name-only -r $(echo "$DIFF") | xargs))"
+        if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
+          echo "skip=true" >> $GITHUB_OUTPUT
+        fi
+        pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
+      env:
+        CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
     - name: Notify that Chromatic will skip testing
       uses: actions/github-script@v6.4.0
-      if: github.ref != 'refs/heads/master' && github.ref != 'refs/heads/develop' && steps.chromatic.outputs.skip == 'true'
+      if: github.event_name == 'pull_request' && steps.chromatic_pull_request.outputs.skip == 'true'
       with:
         github-token: ${{ secrets.GITHUB_TOKEN }}
         script: |
-          github.rest.repos.createCommitComment({
+          github.rest.issues.createComment({
+            issue_number: context.issue.number,
             owner: context.repo.owner,
             repo: context.repo.repo,
-            commit_sha: context.sha,
             body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).'
           })
     - name: Upload Artifacts
diff --git a/.github/workflows/test-backend.yml b/.github/workflows/test-backend.yml
index f1e414dbbc25bc9eaaa670c453b11f4bcfab19f2..d7be15bd4f0966d944111874796a9f0063e0f917 100644
--- a/.github/workflows/test-backend.yml
+++ b/.github/workflows/test-backend.yml
@@ -35,7 +35,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v2
       with:
-        version: 7
+        version: 8
         run_install: false
     - name: Use Node.js ${{ matrix.node-version }}
       uses: actions/setup-node@v3.6.0
diff --git a/.github/workflows/test-frontend.yml b/.github/workflows/test-frontend.yml
index a5505d30d8694c15fc779977543758931bf6e92a..4ea4ba462873e34217faf6008a02eea95dc22e26 100644
--- a/.github/workflows/test-frontend.yml
+++ b/.github/workflows/test-frontend.yml
@@ -22,7 +22,7 @@ jobs:
     - name: Install pnpm
       uses: pnpm/action-setup@v2
       with:
-        version: 7
+        version: 8
         run_install: false
     - name: Use Node.js ${{ matrix.node-version }}
       uses: actions/setup-node@v3.6.0
@@ -106,7 +106,7 @@ jobs:
         install: false
         start: pnpm start:test
         wait-on: 'http://localhost:61812'
-        headless: false
+        headed: true
         browser: ${{ matrix.browser }}
     - uses: actions/upload-artifact@v2
       if: failure()
diff --git a/.github/workflows/test-production.yml b/.github/workflows/test-production.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5243a837778b189b61f14e70871aa9dbc5d9b760
--- /dev/null
+++ b/.github/workflows/test-production.yml
@@ -0,0 +1,42 @@
+name: Test (production install and build)
+
+on:
+  push:
+    branches:
+      - master
+      - develop
+  pull_request:
+
+env:
+  NODE_ENV: production
+
+jobs:
+  production:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [18.x]
+
+    steps:
+    - uses: actions/checkout@v3.3.0
+      with:
+        submodules: true
+    - name: Install pnpm
+      uses: pnpm/action-setup@v2
+      with:
+        version: 8
+        run_install: false
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v3.6.0
+      with:
+        node-version: ${{ matrix.node-version }}
+        cache: 'pnpm'
+    - run: corepack enable
+    - run: pnpm i --frozen-lockfile
+    - name: Check pnpm-lock.yaml
+      run: git diff --exit-code pnpm-lock.yaml
+    - name: Copy Configure
+      run: cp .github/misskey/test.yml .config/default.yml
+    - name: Build
+      run: pnpm build
diff --git a/.gitignore b/.gitignore
index fbe224550263887449da212005be5e88a812c91a..537232d37fc47d3de5c0d0609ec9bc0a364cebae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,7 +44,7 @@ built
 /data
 /.cache-loader
 /db
-/elasticsearch
+/meili_data
 npm-debug.log
 *.pem
 run.bat
diff --git a/.node-version b/.node-version
index 0e9dc6b586773815d114216e9e3a954c1c843eec..6d80269a4f04adb39604e8186baf6b7f62eef1b2 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-v18.13.0
+18.16.0
diff --git a/.vscode/settings.json b/.vscode/settings.json
index baffbe18ecdd1a02a21af9477e9b522ef5abc17e..71fb02a59dfa3234ab67d32bccd645549ade1929 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,5 +6,6 @@
     "files.associations": {
         "*.test.ts": "typescript"
     },
+    "jest.jestCommandLine": "pnpm run jest",
     "jest.autoRun": "off"
 }
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 011e1cbccf292fa65d640e8441c0ced654edc60b..003e907c5fc4589a472874819e099b0cc6892d6e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,64 @@
 -
 
 -->
+## 13.12.0
+
+### NOTE
+- Node.js 18.6.0以上が必要になりました
+
+### General
+- アカウントの引っ越し(フォロワー引き継ぎ)に対応
+- Meilisearchを全文検索に使用できるようになりました
+- 新規登録前に簡潔なルールをユーザーに表示できる、サーバールール機能を追加
+- ユーザーへの自分用メモ機能
+  * ユーザーに対して、自分だけが見られるメモを追加できるようになりました。  
+    (自分自身に対してもメモを追加できます。)
+  * ユーザーメニューから追加できます。  
+    (デスクトップ表示ではusernameの右側のボタンからも追加可能)
+- チャンネルに色を設定できるようになりました。各ノートに設定した色のインジケーターが表示されます。
+- チャンネルをアーカイブできるようになりました。
+	* アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。
+- アンテナのエクスポート・インポートができるようになりました
+- ロールタイムラインをロールごとに表示するかどうかの選択できるようになりました。
+	* デフォルトがオフになるので、ロールタイムラインを表示する場合はオンにしてください。
+- ロールに強制的にNSFWを付与するポリシーを追加
+	* アップロード済みのファイルはNSFWにならない為注意してください。
+- モデレーションノートがユーザーのプロフィールページからも閲覧および編集できるようになりました。
+- カスタム絵文字のライセンスを複数でセットできるようになりました。
+- 管理者が予約ユーザー名を設定できるようになりました。
+- Fix: フォローリクエストの通知が残る問題を修正
+
+### Client
+- アカウント作成時に初期設定ウィザードを表示するように
+- チャンネル内検索ができるように
+- チャンネル検索ですべてのチャンネルの取得/表示ができるように
+- 通知の表示をカスタマイズできるように
+- ドライブのファイル一覧から直接ノートを作成できるように
+- ノートメニューからRenoteしたユーザーの一覧を見れるように
+- コントロールパネルのカスタム絵文字ページおよびaboutのカスタム絵文字の検索インプットで、`:emojiname1::emojiname2:`のように検索して絵文字を検索できるように
+  * 絵文字ピッカーから入力可能になります
+- データセーバーモードを追加
+  * 画像が全て隠れた状態で表示されるようになります
+- 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
+- モデレーターはノートに添付された画像上から直接NSFW設定できるように
+- プロフィール設定「追加情報」の項目の削除と並び替えができるように
+- 新しい実績を追加
+- AiScriptを0.13.2に更新
+- Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正
+- Fix: 1:1ではない画像のリアクション通知バッジが左や上に寄ってしまっていたのを中央に来るように修正
+- Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正
+- Fix: NSFWメディアの上に表示された「もっと見る」ボタンが押しづらい問題を修正
+
+### Server
+- channel/searchのqueryが空の場合に全てのチャンネルを返すように変更
+- 環境変数MISSKEY_CONFIG_YMLで設定ファイルをdefault.ymlから変更可能に
+- Fix: 他のサーバーの情報が取得できないことがある問題を修正
+- Fix: エクスポートデータの拡張子がunknownになる問題を修正
+- Fix: Content-Dispositionのパースでエラーが発生した場合にダウンロードが完了しない問題を修正
+- Fix: API: i/update avatarIdとbannerIdにnullを渡した時、画像がリセットされない問題を修正
+- Fix: .wav, .flacが再生できない問題を修正(新しくアップロードされたファイルのみ修正が適用されます)
+- Fix: 凍結されたユーザーが一部APIのレスポンスに含まれる問題を修正
+- Fix: メモリの使用量を`used - buffers - cached`ではなく`total - available`で求めるように(環境によって正常に計測できていなかったため)
 
 ## 13.11.3
 
@@ -107,6 +165,8 @@
 	  - 猫耳のアバター内部部分をぼかしでマスク表示してより猫耳っぽく見えるように
 	- 「UIのアニメーションを減らす」 (`reduceAnimation`) で猫耳を撫でられなくなります
 - Add Minimizing ("folding") of windows
+- 「データセーバー」モードを追加
+- 非NSFWメディアが隠れている際にも「閲覧注意」が出てしまう問題を修正
 
 ### Server
 - PostgreSQLのレプリケーション対応
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fece05d7a94060792b8ebca1e6f2b87af0ffb785..f6b3804f84f988df1fb755f214e167c783f82fc3 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -165,6 +165,11 @@ pnpm jest -- foo.ts
 ### e2e tests
 TODO
 
+## Environment Variable
+
+- `MISSKEY_CONFIG_YML`: Specify the file path of config.yml instead of default.yml (e.g. `2nd.yml`).
+- `MISSKEY_WEBFINGER_USE_HTTP`: If it's set true, WebFinger requests will be http instead of https, useful for testing federation between servers in localhost. NEVER USE IN PRODUCTION.
+
 ## Continuous integration
 Misskey uses GitHub Actions for executing automated tests.
 Configuration files are located in [`/.github/workflows`](/.github/workflows).
@@ -245,7 +250,6 @@ You can override the default story by creating a impl story file (`MyComponent.s
 
 ```ts
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-/* eslint-disable import/no-duplicates */
 import { StoryObj } from '@storybook/vue3';
 import MyComponent from './MyComponent.vue';
 export const Default = {
diff --git a/Dockerfile b/Dockerfile
index 8db7400c9fa766bb21e6c294b025cc8f18e33ccc..fb389659bc9378f5b44b1ecfc715728fd8da60bd 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
 # syntax = docker/dockerfile:1.4
 
-ARG NODE_VERSION=18.13.0-bullseye
+ARG NODE_VERSION=18.16.0-bullseye
 
 # build assets & compile TypeScript
 
diff --git a/chart/files/default.yml b/chart/files/default.yml
index 1888669245db3b39f819e7a9561f4e04079602b2..342c6091f015510b93c1de19a80eb9919cfc038b 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -116,15 +116,14 @@ redis:
 #  #prefix: example-prefix
 #  #db: 1
 
-#   ┌─────────────────────────────┐
-#───┘ Elasticsearch configuration └─────────────────────────────
+#   ┌───────────────────────────┐
+#───┘ MeiliSearch configuration └─────────────────────────────
 
-#elasticsearch:
+#meilisearch:
 #  host: localhost
-#  port: 9200
-#  ssl: false
-#  user:
-#  pass:
+#  port: 7700
+#  apiKey: ''
+#  ssl: true
 
 #   ┌───────────────┐
 #───┘ ID generation └───────────────────────────────────────────
diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js
index 8dc07c180066b0d292c1e34c251099991bb9b68c..73f6e7a0f0e50e94266c7a7f6f5390bd374e4c35 100644
--- a/cypress/e2e/basic.cy.js
+++ b/cypress/e2e/basic.cy.js
@@ -52,6 +52,11 @@ describe('After setup instance', () => {
 		cy.intercept('POST', '/api/signup').as('signup');
 
 		cy.get('[data-cy-signup]').click();
+		cy.get('[data-cy-signup-rules-continue]').should('be.disabled');
+		cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click();
+		cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled');
+		cy.get('[data-cy-signup-rules-continue]').click();
+	
 		cy.get('[data-cy-signup-submit]').should('be.disabled');
 		cy.get('[data-cy-signup-username] input').type('alice');
 		cy.get('[data-cy-signup-submit]').should('be.disabled');
@@ -71,6 +76,11 @@ describe('After setup instance', () => {
 
 		// ユーザー名が重複している場合の挙動確認
 		cy.get('[data-cy-signup]').click();
+		cy.get('[data-cy-signup-rules-continue]').should('be.disabled');
+		cy.get('[data-cy-signup-rules-notes-agree] [data-cy-switch-toggle]').click();
+		cy.get('[data-cy-signup-rules-continue]').should('not.be.disabled');
+		cy.get('[data-cy-signup-rules-continue]').click();
+
 		cy.get('[data-cy-signup-username] input').type('alice');
 		cy.get('[data-cy-signup-password] input').type('alice1234');
 		cy.get('[data-cy-signup-password-retype] input').type('alice1234');
@@ -149,10 +159,55 @@ describe('After user signed in', () => {
 	});
 
   it('successfully loads', () => {
-		cy.get('[data-cy-open-post-form]').should('be.visible');
+		cy.get('[data-cy-user-setup-continue]').should('be.visible');
+  });
+
+	it('account setup wizard', () => {
+		cy.get('[data-cy-user-setup-continue]').click();
+
+		cy.get('[data-cy-user-setup-user-name] input').type('ありす');
+		cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
+		// TODO: アイコン設定テスト
+
+		cy.get('[data-cy-user-setup-continue]').click();
+
+		// フォローはスキップ
+
+		cy.get('[data-cy-user-setup-continue]').click();
+
+		// プッシュ通知設定はスキップ
+
+		cy.get('[data-cy-user-setup-continue]').click();
+
+		cy.get('[data-cy-user-setup-continue]').click();
   });
+});
+
+describe('After user setup', () => {
+	beforeEach(() => {
+		cy.resetState();
+
+		// インスタンス初期セットアップ
+		cy.registerUser('admin', 'pass', true);
+
+		// ユーザー作成
+		cy.registerUser('alice', 'alice1234');
+
+		cy.login('alice', 'alice1234');
+
+		// アカウント初期設定ウィザード
+		cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click();
+		cy.get('[data-cy-modal-dialog-ok]').click();
+	});
+
+	afterEach(() => {
+		// テスト終了直前にページ遷移するようなテストケース(例えばアカウント作成)だと、たぶんCypressのバグでブラウザの内容が次のテストケースに引き継がれてしまう(例えばアカウントが作成し終わった段階からテストが始まる)。
+		// waitを入れることでそれを防止できる
+		cy.wait(1000);
+	});
 
 	it('note', () => {
+		cy.get('[data-cy-open-post-form]').should('be.visible');
 		cy.get('[data-cy-open-post-form]').click();
 		cy.get('[data-cy-post-form-text]').type('Hello, Misskey!');
 		cy.get('[data-cy-open-post-form-submit]').click();
diff --git a/cypress/e2e/widgets.cy.js b/cypress/e2e/widgets.cy.js
index a39ea85e1d82b153e90e344e21708cbc5150ba5e..f5a982eb0a6fa2af9f2acd9238f3778641c79210 100644
--- a/cypress/e2e/widgets.cy.js
+++ b/cypress/e2e/widgets.cy.js
@@ -10,6 +10,10 @@ describe('After user signed in', () => {
 		cy.registerUser('alice', 'alice1234');
 
 		cy.login('alice', 'alice1234');
+
+		// アカウント初期設定ウィザード
+		cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click();
+		cy.get('[data-cy-modal-dialog-ok]').click();
 	});
 
 	afterEach(() => {
@@ -19,26 +23,26 @@ describe('After user signed in', () => {
 	});
 
   it('widget edit toggle is visible', () => {
-		cy.get('.mk-widget-edit').should('be.visible');
+		cy.get('[data-cy-widget-edit]').should('be.visible');
   });
 
 	it('widget select should be visible in edit mode', () => {
-		cy.get('.mk-widget-edit').click();
-		cy.get('.mk-widget-select').should('be.visible');
+		cy.get('[data-cy-widget-edit]').click();
+		cy.get('[data-cy-widget-select]').should('be.visible');
   });
 
 	it('first widget should be removed', () => {
-		cy.get('.mk-widget-edit').click();
+		cy.get('[data-cy-widget-edit]').click();
 		cy.get('[data-cy-customize-container]:first-child [data-cy-customize-container-remove]._button').click();
 		cy.get('[data-cy-customize-container]').should('have.length', 2);
 	});
 
 	function buildWidgetTest(widgetName) {
 		it(`${widgetName} widget should get added`, () => {
-			cy.get('.mk-widget-edit').click();
-			cy.get('.mk-widget-select select').select(widgetName, { force: true });
+			cy.get('[data-cy-widget-edit]').click();
+			cy.get('[data-cy-widget-select] select').select(widgetName, { force: true });
 			cy.get('[data-cy-bg]._modalBg[data-cy-transparent]').click({ multiple: true, force: true });
-			cy.get('.mk-widget-add').click({ force: true });
+			cy.get('[data-cy-widget-add]').click({ force: true });
 			cy.get(`[data-cy-mkw-${widgetName}]`).should('exist');
 		});
 	}
diff --git a/docker-compose.yml.example b/docker-compose.yml.example
index b0c4a914d56612dde403b1a7030bfc0a9827c952..a0061c5c20b17f0e2df0b19b86cd83a19fd76ec3 100644
--- a/docker-compose.yml.example
+++ b/docker-compose.yml.example
@@ -7,7 +7,7 @@ services:
     links:
       - db
       - redis
-#      - es
+#     - meilisearch
     depends_on:
       db:
         condition: service_healthy
@@ -48,16 +48,18 @@ services:
       interval: 5s
       retries: 20
 
-#  es:
+#  meilisearch:
 #    restart: always
-#    image: docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.2
+#    image: getmeili/meilisearch:v1.1.1
 #    environment:
-#      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
-#      - "TAKE_FILE_OWNERSHIP=111"
+#      - MEILI_NO_ANALYTICS=true
+#      - MEILI_ENV=production
+#    env_file:
+#      - .config/meilisearch.env
 #    networks:
 #      - internal_network
 #    volumes:
-#      - ./elasticsearch:/usr/share/elasticsearch/data
+#      - ./meili_data:/meili_data
 
 networks:
   internal_network:
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 4b9062051bc02450f129f98c6f6f7407b8151709..9a5936660e7b9213c138ee37aedfc0fda3e65922 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -250,12 +250,12 @@ noMoreHistory: "لا يوجد المزيد من التاريخ"
 startMessaging: "ابدأ محادثة"
 nUsersRead: "قرأه {n}"
 agreeTo: "اوافق على {0}"
-tos: "شروط الخدمة"
 start: "البداية"
 home: "الرئيسي"
 remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
 activity: "النشاط"
 images: "الصور"
+image: "الصور"
 birthday: "تاريخ الميلاد"
 yearsOld: "{age} سنة"
 registeredDate: "انضم في"
@@ -801,6 +801,8 @@ cannotLoad: "تعذر التحميل"
 like: "أعجبني"
 show: "المظهر"
 color: "اللون"
+horizontal: "جانبي"
+youFollowing: "متابَع"
 _role:
   priority: "الأولوية"
   _priority:
@@ -977,29 +979,6 @@ _time:
   minute: "د"
   hour: "سا"
   day: "ÙŠ"
-_tutorial:
-  title: "كيف تستخدم Misskey"
-  step1_1: "مرحبًا!"
-  step1_2: "تدعى هذه الصفحة 'الخيط الزمني' وهي تحوي ملاحظات الأشخاص الذي تتابعهم مرتبة حسب تاريخ نشرها."
-  step1_3: "خيطك الزمني فارغ حاليًا بما أنك لا تتابع أي شخص ولم تنشر أي ملاحظة."
-  step2_1: "لننهي إعداد ملفك الشخصي قبل كتابة ملاحظة أو متابعة أشخاص."
-  step2_2: "أعطاء معلومات عن شخصيتك يمنح من له نفس إهتماماتك فرصة متابعتك والتفاعل مع ملاحظاتك."
-  step3_1: "هل أنهيت إعداد حسابك؟"
-  step3_2: "إذا تاليًا لتنشر ملاحظة. أنقر على أيقونة القلم في أعلى الشاشة"
-  step3_3: "املأ النموذج وانقر الزرّ الموجود في أعلى اليمين للإرسال."
-  step3_4: "ليس لديك ما تقوله؟ إذا اكتب \"بدأتُ استخدم ميسكي\"."
-  step4_1: "هل نشرت ملاحظتك الأولى؟"
-  step4_2: "مرحى! يمكنك الآن رؤية ملاحظتك في الخيط الزمني."
-  step5_1: "والآن، لنجعل الخيط الزمني أكثر حيوية وذلك بمتابعة بعض المستخدمين."
-  step5_2: "تعرض صفحة {features} الملاحظات المتداولة في هذا المثيل ويتيح لك {Explore} العثور على المستخدمين الرائدين. اعثر على الأشخاص الذين يثيرون إهتمامك وتابعهم!"
-  step5_3: "لمتابعة مستخدمين ادخل ملفهم الشخصي بالنقر على صورتهم الشخصية ثم اضغط زر 'تابع'."
-  step5_4: "إذا كان لدى المستخدم رمز قفل بجوار اسمه ، وجب عليك انتظاره ليقبل طلب المتابعة يدويًا."
-  step6_1: "الآن ستتمكن من رؤية ملاحظات المستخدمين المتابَعين في الخيط الزمني."
-  step6_2: "يمكنك التفاعل بسرعة مع الملاحظات عن طريق إضافة \"تفاعل\"."
-  step6_3: "لإضافة تفاعل لملاحظة ، انقر فوق علامة \"+\" أسفل للملاحظة واختر الإيموجي المطلوب."
-  step7_1: "مبارك ! أنهيت الدورة التعليمية الأساسية لاستخدام ميسكي."
-  step7_2: "إذا أردت معرفة المزيد عن ميسكي زر {help}."
-  step7_3: "حظًا سعيدًا واستمتع بوقتك مع ميسكي! 🚀"
 _2fa:
   alreadyRegistered: "سجلت سلفًا جهازًا للاستيثاق بعاملين."
   step1: "أولًا ثبّت تطبيق استيثاق على جهازك (مثل {a} و{b})."
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 734943960c8827df80fcde3e1404fab05367ebb5..4b1a9cb758f7ea98467b930201a44665ff336822 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -252,12 +252,12 @@ noMoreHistory: "আর কোন ইতিহাস নেই"
 startMessaging: "চ্যাট শুরু করুন"
 nUsersRead: "{n} জন পড়েছেন"
 agreeTo: "{0} এর প্রতি আমি সম্মত"
-tos: "পরিষেবার শর্তাদি"
 start: "শুরু করুন"
 home: "মূল পাতা"
 remoteUserCaution: "এই ব্যাবহারকারী রিমোট ইন্সট্যান্সের, নিম্নক্ত তথ্য অসম্পূর্ণ হতে পারে।"
 activity: "কার্যকলাপ"
 images: "ছবি"
+image: "ছবি"
 birthday: "জন্মদিন"
 yearsOld: "{age} বছর"
 registeredDate: "যোগদানের তারিখ"
@@ -836,6 +836,8 @@ account: "অ্যাকাউন্টগুলি"
 like: "পছন্দ করা"
 show: "প্রদর্শন"
 color: "রং"
+horizontal: "পাশে"
+youFollowing: "অনুসরণ করা হচ্ছে"
 _role:
   priority: "অগ্রাধিকার"
   _priority:
@@ -1039,29 +1041,6 @@ _time:
   minute: "মিনিট"
   hour: "ঘণ্টা"
   day: "দিন"
-_tutorial:
-  title: "Misskey কিভাবে ব্যাবহার করবেন"
-  step1_1: "স্বাগতম!"
-  step1_2: "এই স্ক্রীনটিকে \"টাইমলাইন\" বলা হয় এবং কালানুক্রমিক ক্রমে আপনার এবং আপনি যাদের \"অনুসরণ করেন\" তাদের \"নোটগুলি\" দেখায়৷"
-  step1_3: "আপনি আপনার টাইমলাইনে কিছু দেখতে পাবেন না কারণ আপনি এখনও কোনো নোট পোস্ট করেননি এবং আপনি কাউকে অনুসরণ করছেন না৷"
-  step2_1: "নোট তৈরি করার আগে বা কাউকে অনুসরণ করার আগে প্রথমে আপনার প্রোফাইলটি সম্পূর্ণ করুন।"
-  step2_2: "আপনি কে তা জানা অনেক লোকের জন্য আপনার নোটগুলি দেখা এবং অনুসরণ করাকে সহজ করে তোলে৷"
-  step3_1: "আপনি কি সফলভাবে আপনার প্রোফাইল সেট আপ করেছেন?"
-  step3_2: "এখন, কিছু নোট পোস্ট করার চেষ্টা করুন। পোস্ট ফর্ম খুলতে পেন্সিল চিহ্নযুক্ত বাটনে ক্লিক করুন।"
-  step3_3: "বিষয়বস্তু লেখার পরে, আপনি ফর্মের উপরের ডানদিকের বাটনে ক্লিক করে পোস্ট করতে পারেন।"
-  step3_4: "পোস্ট করার মত কিছু মনে পরছে না? \"আমি মিসকি সেট আপ করছি\" বললে কেমন হয়?"
-  step4_1: "পোস্ট করেছেন?"
-  step4_2: "সাবাশ! এখন আপনার নোট টাইমলাইনে দেখা যাবে।"
-  step5_1: "এখন অন্যদেরকে অনুসরণ করে আপনার টাইমলাইনকে প্রাণবন্ত করে তুলুন।"
-  step5_2: "আপনি {featured}-এ জনপ্রিয় নোটগুলি দেখতে পারেন, যাতে আপনি যে ব্যক্তিকে পছন্দ করেন তাকে বেছে নিতে এবং অনুসরণ করতে পারেন, অথবা {explore}-এ জনপ্রিয় ব্যবহারকারীদের দেখতে পারেন৷"
-  step5_3: "একজন ব্যবহারকারীকে অনুসরণ করতে, ব্যবহারকারীর আইকনে ক্লিক করুন এবং ব্যবহারকারীর পৃষ্ঠাতে  \"অনুসরণ করুন\" বাটনে ক্লিক করুন।"
-  step5_4: "যদি ব্যবহারকারীর নামের পাশে একটি লক আইকন থাকে তাহলে আপনার অনুসরণের অনুরোধ গ্রহণ করার জন্য তারা কিছু সময় নিতে পারে।"
-  step6_1: "সবকিছু ঠিক থাকলে আপনি টাইমলাইনে অন্য ব্যবহারকারীদের নোট দেখতে পাবেন।"
-  step6_2: "আপনি সহজেই আপনার প্রতিক্রিয়া জানাতে অন্য ব্যক্তির নোটে \"রিঅ্যাকশন\" যোগ করতে পারেন।"
-  step6_3: "একটি রিঅ্যাকশন যোগ করতে, নোটে \"+\" চিহ্নে ক্লিক করুন এবং আপনার পছন্দের রিঅ্যাকশন নির্বাচন করুন।"
-  step7_1: "অভিনন্দন! আপনি এখন Misskey-র প্রাথমিক টিউটোরিয়ালটি শেষ করেছেন।"
-  step7_2: "আপনি যদি Misskey সম্পর্কে আরও জানতে চান, তাহলে {help} এ দেখুন।"
-  step7_3: "এখন Misskey উপভোগ করুন 🚀"
 _2fa:
   alreadyRegistered: "আপনি ইতিমধ্যে একটি 2-ফ্যাক্টর অথেনটিকেশন ডিভাইস নিবন্ধন করেছেন৷"
   step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷"
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index 2b1168f7803f5c53fc88f7b78e0cfb140e8ebde3..588a07fd8794b36884b29126eb8bfbaaec37a106 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -216,6 +216,7 @@ instanceFollowers: "Seguidors del servidor"
 instanceUsers: "Usuaris del servidor"
 changePassword: "Canvia la contrasenya"
 security: "Seguretat"
+retypedNotMatch: "L'entrada no coincideix"
 currentPassword: "Contrasenya actual"
 newPassword: "Contrasenya nova"
 newPasswordRetype: "Contrasenya nou (repeteix-la)"
@@ -239,6 +240,7 @@ start: "Comença"
 home: "Inici"
 activity: "Activitat"
 images: "Imatges"
+image: "Imatges"
 birthday: "Aniversari"
 yearsOld: "{age} anys"
 registeredDate: "Data de registre"
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index 19815c6f37baa7c13fad7003e85aa85e2c984903..d055fde38fea1da0643eec7a6294ca0925a39ab4 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -12,12 +12,14 @@ fetchingAsApObject: "Načítám data z Fediversu..."
 ok: "Potvrdit"
 gotIt: "Rozumím!"
 cancel: "Zrušit"
+noThankYou: "Ne děkuji"
 enterUsername: "Zadej uživatelské jméno"
 renotedBy: "{user} přeposla/a"
 noNotes: "Žádné poznámky"
 noNotifications: "Žádná oznámení"
 instance: "Instance"
 settings: "Nastavení"
+notificationSettings: "Nastavení oznámení"
 basicSettings: "Obecná nastavení"
 otherSettings: "Rozšířená nastavení"
 openInWindow: "Otevřít v novém okně"
@@ -236,12 +238,12 @@ noMoreHistory: "To je vše"
 startMessaging: "Zahájit chat"
 nUsersRead: "přečteno {n} uživateli"
 agreeTo: "Souhlasím s {0}"
-tos: "Podmínky užívání"
 start: "Začít"
 home: "Domů"
 remoteUserCaution: "Tyto informace nemusí být aktuální jelikož uživatel je ze vzdálené instance."
 activity: "Aktivita"
 images: "Obrázky"
+image: "Obrázky"
 birthday: "Datum narození"
 yearsOld: "{age} let"
 registeredDate: "Datum registrace"
@@ -664,6 +666,8 @@ _time:
   second: "Sekund"
   minute: "Minut"
   hour: "Hodin"
+_2fa:
+  renewTOTPCancel: "Ne děkuji"
 _weekday:
   sunday: "Neděle"
   monday: "Pondělí"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 095c71642aa8583fbbcb14e3960696ff3b95303f..d678fadd4aefc7d9ed8d0809778b78970f5e28e7 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -263,14 +263,16 @@ noMoreHistory: "Kein weiterer Verlauf vorhanden"
 startMessaging: "Neuen Chat erstellen"
 nUsersRead: "Von {n} Benutzern gelesen"
 agreeTo: "Ich stimme {0} zu"
+agree: "Zustimmen"
 agreeBelow: "Ich stimme Untenstehendem zu"
 basicNotesBeforeCreateAccount: "Wichtige Infos"
-tos: "Nutzungsbedingungen"
+termsOfService: "Nutzungsbedingungen"
 start: "Anfangen"
 home: "Startseite"
 remoteUserCaution: "Diese Informationen sind möglicherweise unvollständig, da der Benutzer von einer fremden Instanz stammt."
 activity: "Aktivität"
 images: "Bilder"
+image: "Bild"
 birthday: "Geburtstag"
 yearsOld: "{age} Jahre alt"
 registeredDate: "Registrationsdatum"
@@ -468,12 +470,14 @@ enableAdvancedMfm: "Erweitertes MFM aktivieren"
 enableAnimatedMfm: "Animiertes MFM aktivieren"
 doing: "In Bearbeitung …"
 category: "Kategorie"
-tags: "Schlagwörter"
+tags: "Aliasse"
 docSource: "Quellcode dieses Dokuments"
 createAccount: "Benutzerkonto erstellen"
 existingAccount: "Bestehendes Benutzerkonto"
 regenerate: "Regenerieren"
 fontSize: "Schriftgröße"
+mediaListWithOneImageAppearance: "Höhe von Medienlisten mit nur einem Bild"
+limitTo: "Auf {x} begrenzen"
 noFollowRequests: "Keine ausstehenden Follow-Anfragen vorhanden"
 openImageInNewTab: "Bilder in neuem Tab öffnen"
 dashboard: "Dashboard"
@@ -556,6 +560,7 @@ accountDeletedDescription: "Dieses Konto wurde gelöscht."
 menu: "Menü"
 divider: "Trenner"
 addItem: "Element hinzufügen"
+rearrange: "Sortieren"
 relays: "Relays"
 addRelay: "Relay hinzufügen"
 inboxUrl: "inbox-URL"
@@ -699,6 +704,8 @@ contact: "Kontakt"
 useSystemFont: "Standardschriftart des Systems verwenden"
 clips: "Clips"
 experimentalFeatures: "Experimentelle Funktionalitäten"
+experimental: "Experimentell"
+thisIsExperimentalFeature: "Dies ist eine experimentelle Funktion. Änderungen an ihrer Funktionsweise sind vorbehalten, zudem kann eine Verwendung zu unerwarteten Effekten führen."
 developer: "Entwickler"
 makeExplorable: "Benutzerkonto in „Erkunden“ sichtbar machen"
 makeExplorableDescription: "Wenn diese Option deaktiviert ist, ist dein Benutzerkonto nicht im „Erkunden“-Bereich sichtbar."
@@ -905,6 +912,7 @@ remoteOnly: "Nur für fremde Instanzen"
 failedToUpload: "Hochladen fehlgeschlagen"
 cannotUploadBecauseInappropriate: "Diese Datei kann nicht hochgeladen werden, da Anteile der Datei als möglicherweise NSFW festgestellt wurden."
 cannotUploadBecauseNoFreeSpace: "Die Datei konnte nicht hochgeladen werden, da dein Drive-Speicherplatz aufgebraucht ist."
+cannotUploadBecauseExceedsFileSizeLimit: "Diese Datei kann wegen Überschreitung der Maximalgröße nicht hochgeladen werden."
 beta: "Beta"
 enableAutoSensitive: "NSFW-Automarkierung"
 enableAutoSensitiveDescription: "Setzt soweit möglich durch Verwendung von Machine Learning automatisch NSFW-Markierungen für Medien, die NSFW-Anteile beinhalten. Auch wenn du diese Option deaktiviert hast, ist sie möglicherweise auf Instanzebene aktiviert."
@@ -918,8 +926,8 @@ subscribePushNotification: "Push-Benachrichtigungen aktivieren"
 unsubscribePushNotification: "Push-Benachrichtigungen deaktivieren"
 pushNotificationAlreadySubscribed: "Push-Benachrichtigungen sind bereits aktiviert"
 pushNotificationNotSupported: "Entweder dein Browser oder deine Instanz unterstützt Push-Benachrichtigungen nicht"
-sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald die relevanten Benachrichtigungen oder Nachrichten gelesen wurden"
-sendPushNotificationReadMessageCaption: "Eine Push-Benachrichtigungen mit dem Inhalt \"{emptyPushNotificationMessage}\" wird kurz eingeblendet. Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen."
+sendPushNotificationReadMessage: "Push-Benachrichtigungen löschen, sobald sie gelesen wurden"
+sendPushNotificationReadMessageCaption: "Dies kann gegebenenfalls den Batterieverbrauch deines Gerätes erhöhen."
 windowMaximize: "Maximieren"
 windowMinimize: "Minimieren"
 windowRestore: "Wiederherstellen"
@@ -938,6 +946,7 @@ didYouLikeMisskey: "Gefällt dir Misskey?"
 pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
 roles: "Rollen"
 role: "Rolle"
+noRole: "Rolle nicht gefunden"
 normalUser: "Standardbenutzer"
 undefined: "Undefiniert"
 assign: "Zuweisen"
@@ -947,6 +956,10 @@ manageCustomEmojis: "Kann benutzerdefinierte Emojis verwalten"
 youCannotCreateAnymore: "Du hast das Erstellungslimit erreicht."
 cannotPerformTemporary: "Vorübergehend nicht verfügbar"
 cannotPerformTemporaryDescription: "Diese Aktion ist wegen des Überschreitenes des Ausführungslimits temporär nicht verfügbar. Bitte versuche es nach einiger Zeit erneut."
+invalidParamError: "Ungültige Parameter"
+invalidParamErrorDescription: "Die Anfrageparameter sind fehlerhaft. Dies liegt meist an einem Bug, kann aber auch durch eine zu langen Eingabe o.ä. ausgelöst werden."
+permissionDeniedError: "Aktion verweigert"
+permissionDeniedErrorDescription: "Dieses Benutzerkonto besitzt nicht die Berechtigung, um diese Aktion auszuführen."
 preset: "Vorlage"
 selectFromPresets: "Aus Vorlagen wählen"
 achievements: "Errungenschaften"
@@ -990,17 +1003,70 @@ enableChartsForFederatedInstances: "Diagramme für fremde Instanzen erstellen"
 showClipButtonInNoteFooter: "\"Clip\" zum Notizmenu hinzufügen"
 largeNoteReactions: "Reaktionen vergrößert anzeigen"
 noteIdOrUrl: "Notiz-ID oder URL"
-accountMigration: "Konto-Umzug"
-accountMoved: "Dieser Benutzer ist zu einem neuen Konto umgezogen:"
+video: "Video"
+videos: "Videos"
+dataSaver: "Datensparmodus"
+accountMigration: "Kontomigration"
+accountMoved: "Dieser Benutzer ist zu einem neuen Konto migriert:"
+accountMovedShort: "Dieses Konto wurde migriert."
+operationForbidden: "Aktion nicht möglich"
 forceShowAds: "Werbung immer anzeigen"
+addMemo: "Bemerkung hinzufügen"
+editMemo: "Bemerkung bearbeiten"
+reactionsList: "Reaktionen"
+renotesList: "Renotes"
+notificationDisplay: "Benachrichtigungen"
+leftTop: "Oben links"
+rightTop: "Oben rechts"
+leftBottom: "Unten links"
+rightBottom: "Unten rechts"
+stackAxis: "Stapelrichtung"
+vertical: "Vertikal"
+horizontal: "Horizontal"
+position: "Position"
+serverRules: "Serverregeln"
+pleaseConfirmBelowBeforeSignup: "Lies bitte Untenstehendes vor der Registration."
+pleaseAgreeAllToContinue: "Zum Fortfahren muss allen obigen Feldern zugestimmt werden."
+continue: "Fortfahren"
+preservedUsernames: "Reservierte Benutzernamen"
+preservedUsernamesDescription: "Gib zu reservierende Benutzernamen durch Zeilenumbrüche getrennt an. Diese werden für die Registrierung gesperrt, können aber von Administratoren zur manuellen Erstellung von Konten verwendet werden. Existierende Konten, die diese Namen bereits verwenden, werden nicht beeinträchtigt."
+createNoteFromTheFile: "Notiz für diese Datei schreiben"
+archive: "Archivieren"
+channelArchiveConfirmTitle: "{name} wirklich archivieren?"
+channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden."
+thisChannelArchived: "Dieser Kanal wurde archiviert."
+displayOfNote: "Anzeige von Notizen"
+initialAccountSetting: "Kontoeinrichtung"
+youFollowing: "Gefolgt"
+_initialAccountSetting:
+  accountCreated: "Dein Konto wurde erfolgreich erstellt!"
+  letsFillYourProfile: "Lass uns zuerst dein Profil einrichten."
+  profileSetting: "Profileinstellungen"
+  theseSettingsCanEditLater: "Diese Einstellungen kannst du jederzeit ändern."
+  youCanEditMoreSettingsInSettingsPageLater: "In den Einstellungen findest du noch viele weitere Optionen. Schau dort später mal vorbei."
+  followUsers: "Folge zuerst ein paar Nutzern, um deine Chronik zu füllen."
+  pushNotificationDescription: "Durch die Aktivierung von Push-Benachrichtigungen kannst du von {name} Benachrichtigungen direkt auf dein Gerät erhalten."
+  initialAccountSettingCompleted: "Kontoeinrichtung abgeschlossen!"
+  haveFun: "Viel Spaß mit {name}!"
+  ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest."
+  skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?"
+_serverRules:
+  description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
 _accountMigration:
-  moveTo: "Dieses Konto zu einem neuen umziehen"
+  moveFrom: "Von einem anderen Konto zu diesem migrieren"
+  moveFromSub: "Alias für ein anderes Konto erstellen"
+  moveFromLabel: "Migrationsursprung #{n}"
+  moveFromDescription: "Um von einem anderen Konto zu diesem zu migrieren, muss zuvor hier ein Alias eingerichtet werden.\nGib das Konto, von dem migriert werden soll, in folgendem Format ein: @username@server.example.com\n\nZum Löschen des Alias kann das Feld leergelassen werden (nicht empfohlen)."
+  moveTo: "Dieses Konto zu einem neuen migrieren"
   moveToLabel: "Umzugsziel:"
-  moveAccountDescription: "Ein Umzug kann nicht rückgängig gemacht werden. Stelle zuerst sicher, dass du auf dem Umzugsziel einen Alias erstellt hast. Gib dann das Umzugsziel in folgendem Format ein: @person@instance.com"
-  moveFrom: "Von einem anderen Konto zu diesem umziehen"
-  moveFromLabel: "Umzugsursprung:"
-  moveFromDescription: "Stelle sicher, dass du auf dem Umzugsursprungskonto einen Alias zu diesem Konto erstellt hast, falls du die Follower des Ursprungskontos übertragen möchtest. Dies muss vor dem Umzug geschehen! Gib dann das Ursprungskonto in folgendem Format an: @person@instance.com"
-  migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden.\n\nÜberprüfe zusätzlich, dass du auf dem Umzugsziel einen Alias eingerichtet hast."
+  moveCannotBeUndone: "Die Migration eines Benutzerkontos ist unwiderruflich."
+  moveAccountDescription: "Hierdurch wird dein Konto zu einem anderen migriert.\n ・Follower von diesem Konto werden automatisch auf das neue Konto migriert\n ・Dieses Konto wird allen Nutzern, denen es derzeit folgt, nicht mehr folgen\n ・Mit diesem Konto können keine neuen Notizen usw. erstellt werden\n\nWährend die Migration der Follower automatisch erfolgt, muss die Migration der Konten, denen du folgst, manuell vorbereitet werden. Exportiere hierzu die Liste der gefolgten Nutzer über das Einstellungsmenu, und importiere diese Liste im neuen Konto. Das gleiche Verfahren gilt für erstellte Listen und stummgeschaltete oder blockierte Nutzer.\n\n(Diese Erklärung gilt für Misskey v13.12.0 oder später. Die Funktionsweise andere ActivityPub-Software, beispielsweise Mastodon,  kann hiervon abweichen.)"
+  moveAccountHowTo: "Um ein Konto zu migrieren, erstelle zuerst auf dem Umzugsziel einen Alias für dieses Konto.\nGib dann das Umzugsziel in folgendem Format ein: @username@server.example.com"
+  startMigration: "Migrieren"
+  migrationConfirm: "Dieses Konto wirklich zu {account} umziehen? Sobald der Umzug beginnt, kann er nicht rückgängig gemacht werden, und dieses Konto nicht wieder im ursprünglichen Zustand verwendet werden."
+  movedAndCannotBeUndone: "\nDieses Konto wurde migriert.\nDiese Aktion ist unwiderruflich."
+  postMigrationNote: "Dieses Konto wird 24 Stunden nach Abschluss der Migration allen Konten, denen es derzeit folgt, nicht mehr folgen.\n\nSowohl die Anzahl der Follower als auch die der Konten, denen dieses Konto folgt, wird dann auf Null gesetzt. Um zu vermeiden, dass Follower dieses Kontos dessen Beiträge, welche nur für Follower bestimmt sind, nicht mehr sehen können, werden sie diesem Konto jedoch weiterhin folgen."
+  movedTo: "Umzugsziel:"
 _achievements:
   earnedAt: "Freigeschaltet am"
   _types:
@@ -1027,7 +1093,7 @@ _achievements:
       title: "Supernotiz"
       description: "10.000 Notizen gesendet"
     _notes20000:
-      title: "Brauche... mehr... Notizen"
+      title: "Brauche... mehr... Notizen..."
       description: "20.000 Notizen gesendet"
     _notes30000:
       title: "Notizen, Notizen, Notizen"
@@ -1171,7 +1237,10 @@ _achievements:
       description: "Du hast einen verborgenen Schatz gefunden"
     _client30min:
       title: "Kurze Pause"
-      description: "Habe Misskey für 30 Minuten geöffnet"
+      description: "Habe Misskey für mindestens 30 Minuten geöffnet"
+    _client60min:
+      title: "Munter mit Misskey"
+      description: "Habe Misskey für mindestens 60 Minuten geöffnet"
     _noteDeletedWithin1min:
       title: "Ups"
       description: "Lösche eine Notiz innerhalb von 1 Minute nachdem sie gesendet wurde"
@@ -1260,6 +1329,8 @@ _role:
   iconUrl: "Icon-URL"
   asBadge: "Als Abzeichen anzeigen"
   descriptionOfAsBadge: "Ist dies aktiviert, so wird das Icon dieser Rolle an der Seite der Namen von Benutzern mit dieser Rolle angezeigt."
+  isExplorable: "Rollenchronik veröffentlichen"
+  descriptionOfIsExplorable: "Ist dies aktiviert, so ist die Rollenchronik dieser Rolle frei zugänglich. Die Chronik von Rollen, welche nicht öffentlich sind, wird auch bei Aktivierung nicht veröffentlicht."
   displayOrder: "Position"
   descriptionOfDisplayOrder: "Je höher die Nummer, desto höher die UI-Position."
   canEditMembersByModerator: "Moderatoren können Benutzern diese Rolle zuweisen"
@@ -1276,6 +1347,7 @@ _role:
     canInvite: "Erstellung von Einladungscodes für diese Instanz"
     canManageCustomEmojis: "Benutzerdefinierte Emojis verwalten"
     driveCapacity: "Drive-Kapazität"
+    alwaysMarkNsfw: "Dateien immer als NSFW markieren"
     pinMax: "Maximale Anzahl an angehefteten Notizen"
     antennaMax: "Maximale Anzahl an Antennen"
     wordMuteMax: "Maximale Zeichenlänge für Wortstummschaltungen"
@@ -1527,32 +1599,16 @@ _time:
   minute: "Minute(n)"
   hour: "Stunde(n)"
   day: "Tag(en)"
-_tutorial:
+_timelineTutorial:
   title: "Wie du Misskey verwendest"
-  step1_1: "Willkommen!"
-  step1_2: "Diese Seite ist die „Chronik“. Sie zeigt dir deine geschrieben „Notizen“ sowie die aller Benutzer, denen du „folgst“, in chronologischer Reihenfolge."
-  step1_3: "Deine Chronik sollte momentan leer sein, da du bis jetzt noch keine Notizen geschrieben hast und auch noch keinen Benutzern folgst."
-  step2_1: "Lass uns zuerst dein Profil vervollständigen, bevor du Notizen schreibst oder jemandem folgst."
-  step2_2: "Informationen darüber, was für eine Person du bist, macht es anderen leichter zu wissen, ob sie deine Notizen sehen wollen und ob sie dir folgen möchten."
-  step3_1: "Mit dem Einrichten deines Profils fertig?"
-  step3_2: "Dann lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon auf dem Bildschirm drückst."
-  step3_3: "Fülle das Fenster aus und drücke auf den Knopf oben rechts zum Senden."
-  step3_4: "Fällt dir nichts ein, das du schreiben möchtest? Versuch's mit \"Hallo Misskey!\""
-  step4_1: "Fertig mit dem Senden deiner ersten Notiz?"
-  step4_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig gemacht."
-  step5_1: "Lass uns nun deiner Chronik etwas mehr Leben einhauchen, indem du einigen anderen Benutzern folgst."
-  step5_2: "{featured} zeigt dir beliebte Notizen dieser Instanz. In {explore} kannst du beliebte Benutzer finden. Schau dort, ob du Benutzer findest, die dich interessieren."
-  step5_3: "Klicke zum Anzeigen des Profils eines Benutzers auf dessen Profilbild und dann auf den \"Folgen\"-Knopf, um diesem zu folgen."
-  step5_4: "Je nach Benutzer kann es etwas Zeit in Anspruch nehmen, bis dieser deine Follow-Anfrage bestätigt."
-  step6_1: "Wenn du nun auch die Notizen anderer Benutzer in deiner Chronik siehst, hast du auch diesmal alles richtig gemacht."
-  step6_2: "Du kannst ebenso „Reaktionen“ verwenden, um schnell auf Notizen anderer Benutzer zu reagieren."
-  step6_3: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol in der Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest."
-  step7_1: "Glückwunsch! Du hast die Einführung in die Verwendung von Misskey abgeschlossen."
-  step7_2: "Wenn du mehr über Misskey lernen möchtest, schau dich im {help}-Bereich um."
-  step7_3: "Und nun, viel Spaß mit Misskey! 🚀"
-  step8_1: "Möchtest du abschließend Push-Benachrichtigungen aktivieren?"
-  step8_2: "Push-Benachrichtigungen erlauben es dir, über Reaktionen, Follows oder Erwähnungen usw. zu erfahren, auch wenn Misskey zu dieser Zeit nicht geöffnet ist."
-  step8_3: "Diese Einstellung kannst du jederzeit ändern."
+  step1_1: "Dieser Bildschirm ist die \"Chronik\". Hier werden alle \"Notizen\" von {name} angezeigt."
+  step1_2: "Es gibt einige verschiedene Chroniken. Beispielsweise werden in der \"Startseite\" alle Notizen von Nutzern, denen du folgst, angezeigt, und in der \"Lokalen Chronik\" werden Notizen aller Nutzer auf {name} angezeigt."
+  step2_1: "Lass uns als nächstes versuchen, eine Notiz zu schreiben. Dies kannst du tun, indem du auf den Knopf mit dem Stift-Icon drückst."
+  step2_2: "Stell dich den anderen vor oder schreibe einfach \"Hallo {name}!\", wenn du darauf keine Lust hast oder dir nichts einfällt."
+  step3_1: "Fertig mit dem Senden deiner ersten Notiz?"
+  step3_2: "Falls deine Notiz nun in deiner Chronik auftaucht, hast du alles richtig gemacht."
+  step4_1: "Notizen können zusätzlich mit \"Reaktionen\" ausgestattet werden."
+  step4_2: "Um eine Reaktion anzufügen, klicke auf das „+“-Symbol einer Notiz und wähle ein Emoji aus, mit dem du reagieren möchtest."
 _2fa:
   alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung registriert."
   registerTOTP: "Authentifizierungs-App registrieren"
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 32964e9982ac6c3b159db2baaa1259a1d2ce3767..41b1ea7c6566d6b67c16a29968bc2749973862aa 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -172,11 +172,11 @@ explore: "Εξερευνήστε"
 messageRead: "Διαβάστηκε"
 startMessaging: "Ξεκινήστε μία συνομιλία"
 nUsersRead: "διαβάστηκε από {n}"
-tos: "Όροι χρήσης"
 start: "Ας αρχίσουμε"
 home: "Κεντρικό"
 activity: "Δραστηριότητα"
 images: "Εικόνες"
+image: "Εικόνες"
 birthday: "Γενέθλια"
 registeredDate: "Έγινε μέλος στις"
 location: "Τοποθεσία"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index e2407bd3e780b612efe3c6359c0ee7fb5d75c65c..ea91bcc0e578b130707112688cef8ae09a7267bf 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -263,14 +263,16 @@ noMoreHistory: "There is no further history"
 startMessaging: "Start a new chat"
 nUsersRead: "read by {n}"
 agreeTo: "I agree to {0}"
+agree: "Agree"
 agreeBelow: "I agree to the below"
 basicNotesBeforeCreateAccount: "Important notes"
-tos: "Terms of Service"
+termsOfService: "Terms of Service"
 start: "Begin"
 home: "Home"
 remoteUserCaution: "As this user is from a remote instance, the shown information may be incomplete."
 activity: "Activity"
 images: "Images"
+image: "Image"
 birthday: "Birthday"
 yearsOld: "{age} years old"
 registeredDate: "Joined on"
@@ -468,12 +470,14 @@ enableAdvancedMfm: "Enable advanced MFM"
 enableAnimatedMfm: "Enable animated MFM"
 doing: "Processing..."
 category: "Category"
-tags: "Tags"
+tags: "Aliases"
 docSource: "Source of this document"
 createAccount: "Create account"
 existingAccount: "Existing account"
 regenerate: "Regenerate"
 fontSize: "Font size"
+mediaListWithOneImageAppearance: "Height of media lists with one image only"
+limitTo: "Limit to {x}"
 noFollowRequests: "You don't have any pending follow requests"
 openImageInNewTab: "Open images in new tab"
 dashboard: "Dashboard"
@@ -556,6 +560,7 @@ accountDeletedDescription: "This account has been deleted."
 menu: "Menu"
 divider: "Divider"
 addItem: "Add Item"
+rearrange: "Rearrange"
 relays: "Relays"
 addRelay: "Add Relay"
 inboxUrl: "Inbox URL"
@@ -699,6 +704,8 @@ contact: "Contact"
 useSystemFont: "Use the system's default font"
 clips: "Clips"
 experimentalFeatures: "Experimental features"
+experimental: "Experimental"
+thisIsExperimentalFeature: "This is an experimental feature. Its functionality is subject to change, and it may not operate as intended."
 developer: "Developer"
 makeExplorable: "Make account visible in \"Explore\""
 makeExplorableDescription: "If you turn this off, your account will not show up in the \"Explore\" section."
@@ -905,6 +912,7 @@ remoteOnly: "Remote only"
 failedToUpload: "Upload failed"
 cannotUploadBecauseInappropriate: "This file could not be uploaded because parts of it have been detected as potentially NSFW."
 cannotUploadBecauseNoFreeSpace: "Upload failed due to lack of Drive capacity."
+cannotUploadBecauseExceedsFileSizeLimit: "This file cannot be uploaded as it exceeds the file size limit."
 beta: "Beta"
 enableAutoSensitive: "Automatic NSFW-Marking"
 enableAutoSensitiveDescription: "Allows automatic detection and marking of NSFW media through Machine Learning where possible. Even if this option is disabled, it may be enabled instance-wide."
@@ -918,8 +926,8 @@ subscribePushNotification: "Enable push notifications"
 unsubscribePushNotification: "Disable push notifications"
 pushNotificationAlreadySubscribed: "Push notifications are already enabled"
 pushNotificationNotSupported: "Your browser or instance does not support push notifications"
-sendPushNotificationReadMessage: "Delete push notifications once the relevant notifications or messages have been read"
-sendPushNotificationReadMessageCaption: "A notification containing the text \"{emptyPushNotificationMessage}\" will be displayed for a short time. This may increase the power consumption of your device."
+sendPushNotificationReadMessage: "Delete push notifications once they have been read"
+sendPushNotificationReadMessageCaption: "This may increase the power consumption of your device."
 windowMaximize: "Maximize"
 windowMinimize: "Minimize"
 windowRestore: "Restore"
@@ -938,6 +946,7 @@ didYouLikeMisskey: "Have you taken a liking to Misskey?"
 pleaseDonate: "{host} uses the free software, Misskey. We would highly appreciate your donations so development of Misskey can continue!"
 roles: "Roles"
 role: "Role"
+noRole: "Role not found"
 normalUser: "Normal user"
 undefined: "Undefined"
 assign: "Assign"
@@ -947,6 +956,10 @@ manageCustomEmojis: "Manage Custom Emojis"
 youCannotCreateAnymore: "You've hit the creation limit."
 cannotPerformTemporary: "Temporarily unavailable"
 cannotPerformTemporaryDescription: "This action cannot be performed temporarily due to exceeding the execution limit. Please wait for a while and then try again."
+invalidParamError: "Invalid parameters"
+invalidParamErrorDescription: "The request parameters are invalid. This is normally caused by a bug, but may also be due to inputs exceeding size limits or similar."
+permissionDeniedError: "Operation denied"
+permissionDeniedErrorDescription: "This account does not have the permission to perform this action."
 preset: "Preset"
 selectFromPresets: "Choose from presets"
 achievements: "Achievements"
@@ -990,17 +1003,70 @@ enableChartsForFederatedInstances: "Generate remote instance data charts"
 showClipButtonInNoteFooter: "Add \"Clip\" to note action menu"
 largeNoteReactions: "Enlargen displayed reactions"
 noteIdOrUrl: "Note ID or URL"
+video: "Video"
+videos: "Videos"
+dataSaver: "Data Saver"
 accountMigration: "Account Migration"
 accountMoved: "This user has moved to a new account:"
+accountMovedShort: "This account has been migrated."
+operationForbidden: "Operation forbidden"
 forceShowAds: "Always show ads"
+addMemo: "Add memo"
+editMemo: "Edit memo"
+reactionsList: "Reactions"
+renotesList: "Renotes"
+notificationDisplay: "Notifications"
+leftTop: "Top left"
+rightTop: "Top right"
+leftBottom: "Bottom left"
+rightBottom: "Bottom right"
+stackAxis: "Stacking direction"
+vertical: "Vertical"
+horizontal: "Horizontal"
+position: "Position"
+serverRules: "Server rules"
+pleaseConfirmBelowBeforeSignup: "Please confirm the below before signing up."
+pleaseAgreeAllToContinue: "You must agree to all above fields to continue."
+continue: "Continue"
+preservedUsernames: "Reserved usernames"
+preservedUsernamesDescription: "List usernames to reserve separated by linebreaks. These will become unable during normal account creation, but can be used by administrators to manually create accounts. Already existing accounts using these usernames will not be affected."
+createNoteFromTheFile: "Compose note from this file"
+archive: "Archive"
+channelArchiveConfirmTitle: "Really archive {name}?"
+channelArchiveConfirmDescription: "An archived channel won't appear in the channel list or search results anymore. New posts can also not be added to it anymore."
+thisChannelArchived: "This channel has been archived."
+displayOfNote: "Note display"
+initialAccountSetting: "Profile configuration"
+youFollowing: "Followed"
+_initialAccountSetting:
+  accountCreated: "Your account was successfully created!"
+  letsFillYourProfile: "First, let's set up your profile."
+  profileSetting: "Profile settings"
+  theseSettingsCanEditLater: "You can always change these settings later."
+  youCanEditMoreSettingsInSettingsPageLater: "There are many more settings you can configure from the \"Settings\" page. Be sure to visit it later."
+  followUsers: "Try following some users that interest you to build up your timeline."
+  pushNotificationDescription: "Enabling push notifications will allow you to receive notifications from {name} directly on your device."
+  initialAccountSettingCompleted: "Profile configuration complete!"
+  haveFun: "Enjoy {name}!"
+  ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
+  skipAreYouSure: "Really skip profile configuration?"
+_serverRules:
+  description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
 _accountMigration:
+  moveFrom: "Migrate another account to this one"
+  moveFromSub: "Create alias to another account"
+  moveFromLabel: "Original Account #{n}"
+  moveFromDescription: "You must create an alias for the account to move from on this account.\nEnter the account to migrate from in the following format: @username@server.example.com\nTo delete the alias, leave the field empty (not recommended)."
   moveTo: "Migrate this account to a different one"
   moveToLabel: "Account to move to:"
-  moveAccountDescription: "This action is irreversible. First, create an alias for this account on the account you wish to move to. Then, enter the account to move to in the following format: @person@instance.com"
-  moveFrom: "Migrate another account to this one"
-  moveFromLabel: "Account to move from:"
-  moveFromDescription: "Create an alias for the account to move from on this account if you wish to transfer its followers. This has to be done before the transfer! Then, enter the account to move to in the following format: @person@instance.com"
-  migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore.\n\nAlso, confirm you've created an alias at the account to migrate to."
+  moveCannotBeUndone: "Account migration cannot be undone."
+  moveAccountDescription: "This will migrate your account to a different one.\n ・Followers from this account will automatically be migrated to the new account\n ・This account will unfollow all users it is currently following\n ・You will be unable to create new notes etc. on this account\n\nWhile migration of followers is automatic, you must manually prepare some steps to migrate the list of users you are following. To do so, carry out a follows export that you will later import on the new account in the settings menu. The same procedure applies to your lists as well as your muted and blocked users.\n\n(This explanation applies to Misskey v13.12.0 and later. Other ActivityPub software, such as Mastodon, might function differently.)"
+  moveAccountHowTo: "To migrate, first create an alias for this account on the account to move to.\nAfter you have created the alias, enter the account to move to in the following format: @username@server.example.com"
+  startMigration: "Migrate"
+  migrationConfirm: "Really migrate this account to {account}? Once started, this process cannot be stopped or taken back, and you will not be able to use this account in its original state anymore."
+  movedAndCannotBeUndone: "\nThis account has been migrated.\nMigration cannot be reversed."
+  postMigrationNote: "This account will unfollow all accounts it is currently following 24 hours after migration finishes.\nBoth the number of follows and followers will then become zero. To avoid your followers from being unable to see followers only posts of this account, they will however continue following this account."
+  movedTo: "Account to move to:"
 _achievements:
   earnedAt: "Unlocked at"
   _types:
@@ -1171,7 +1237,10 @@ _achievements:
       description: "You've found the hidden treasure"
     _client30min:
       title: "Short break"
-      description: "Spend 30 minutes on Misskey"
+      description: "Keep Misskey opened for at least 30 minutes"
+    _client60min:
+      title: "No \"Miss\" in Misskey"
+      description: "Keep Misskey opened for at least 60 minutes"
     _noteDeletedWithin1min:
       title: "Nevermind"
       description: "Delete a note within a minute of posting it"
@@ -1260,6 +1329,8 @@ _role:
   iconUrl: "Icon URL"
   asBadge: "Show as badge"
   descriptionOfAsBadge: "This role's icon will be displayed next to the username of users with this role if turned on."
+  isExplorable: "Role timeline is public"
+  descriptionOfIsExplorable: "This role's timeline will become publicly accessible if enabled. Timelines of non-public roles will not be made public even if set."
   displayOrder: "Position"
   descriptionOfDisplayOrder: "The higher the number, the higher its UI position."
   canEditMembersByModerator: "Allow moderators to edit the list of members for this role"
@@ -1276,6 +1347,7 @@ _role:
     canInvite: "Can create instance invite codes"
     canManageCustomEmojis: "Can manage custom emojis"
     driveCapacity: "Drive capacity"
+    alwaysMarkNsfw: "Always mark files as NSFW"
     pinMax: "Maximum number of pinned notes"
     antennaMax: "Maximum number of antennas"
     wordMuteMax: "Maximum number of characters allowed in word mutes"
@@ -1527,32 +1599,16 @@ _time:
   minute: "Minute(s)"
   hour: "Hour(s)"
   day: "Day(s)"
-_tutorial:
+_timelineTutorial:
   title: "How to use Misskey"
-  step1_1: "Welcome!"
-  step1_2: "This page is called the \"timeline\". It shows chronologically ordered \"notes\" of people who you \"follow\"."
-  step1_3: "Your timeline is currently empty, since you have not posted any notes or followed anyone yet."
-  step2_1: "Let's finish setting up your profile before writing a note or following anyone."
-  step2_2: "Providing some information about who you are will make it easier for others to tell if they want to see your notes or follow you."
-  step3_1: "Finished setting up your profile?"
-  step3_2: "Then let's try posting a note next. You can do so by pressing the button with a pencil icon on the screen."
-  step3_3: "Fill in the modal and press the button on the top right to post."
-  step3_4: "Have nothing to say? Try \"just setting up my msky\"!"
-  step4_1: "Finished posting your first note?"
-  step4_2: "Hurray! Now your first note should be displayed on your timeline."
-  step5_1: "Now, let's try making your timeline more lively by following other people."
-  step5_2: "{featured} will show you popular notes in this instance. {explore} will let you find popular users. Try finding people you'd like to follow there!"
-  step5_3: "To follow other users, click on their icon and press the \"Follow\" button on their profile."
-  step5_4: "If the other user has a lock icon next to their name, it may take some time for that user to manually approve your follow request."
-  step6_1: "You should be able to see other users' notes on your timeline now."
-  step6_2: "You can also put \"reactions\" on other people's notes to quickly respond to them."
-  step6_3: "To attach a \"reaction\", press the \"+\" mark on another user's note and choose an emoji you'd like to react with."
-  step7_1: "Congratulations! You have now finished Misskey's basic tutorial."
-  step7_2: "If you would like to learn more about Misskey, try the {help} section."
-  step7_3: "Now then, have fun with Misskey! 🚀"
-  step8_1: "Lastly, would you like to enable push notifications?"
-  step8_2: "Enabling these will allow you to receive notifications for mentions, reactions, follows, etc. even when Misskey is not opened."
-  step8_3: "You can always change this setting later."
+  step1_1: "This is the \"timeline\". All \"notes\" submitted on {name} will be chronologically displayed here."
+  step1_2: "There are a few different timelines. For example, the \"Home timeline\" will contain notes of users you follow, and the \"Local timeline\" will contain notes from all users of {name}."
+  step2_1: "Let's try posting a note next. You can do so by pressing the button with a pencil icon."
+  step2_2: "How about writing a self-introduction, or just \"Hello {name}!\" if you don't feel like it?"
+  step3_1: "Finished posting your first note?"
+  step3_2: "Your first note should now be displayed on your timeline."
+  step4_1: "You can also attach \"Reactions\" to notes."
+  step4_2: "To attach a reaction, press the \"+\" mark on a note and choose an emoji you'd like to react with."
 _2fa:
   alreadyRegistered: "You have already registered a 2-factor authentication device."
   registerTOTP: "Register authenticator app"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 29cf9b3ec9637948036e7c42dd0092f26405f63c..42d52e5a2b795712def58c5cfe7ff9ed8b5d9ebd 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -264,12 +264,12 @@ nUsersRead: "Leído por {n} personas"
 agreeTo: "De acuerdo con {0}"
 agreeBelow: "Estoy de acuerdo con lo siguiente"
 basicNotesBeforeCreateAccount: "Notas básicas"
-tos: "Términos de uso"
 start: "Comenzar"
 home: "Inicio"
 remoteUserCaution: "Para el usuario remoto, la información está incompleta"
 activity: "Actividad"
 images: "Imágenes"
+image: "Imágenes"
 birthday: "Fecha de nacimiento"
 yearsOld: "{age} años"
 registeredDate: "Fecha de registro"
@@ -991,14 +991,17 @@ largeNoteReactions: "Agrandar las reacciones de las notas"
 noteIdOrUrl: "ID o URL de la nota"
 accountMigration: "Migración de cuenta"
 accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
+horizontal: "Horizontal"
+youFollowing: "Siguiendo"
 _accountMigration:
-  moveTo: "Mover esta cuenta a una nueva"
-  moveToLabel: "Cuenta destino:"
-  moveAccountDescription: "Esta operación no puede deshacerse. En primer lugar, asegúrese de haber creado un alias para esta cuenta en la cuenta a la que se va a trasladar. Después de crear el alias, introduzca la cuenta a la que se está trasladando de la siguiente manera: @person@instance.com"
   moveFrom: "Trasladar de otra cuenta a ésta"
   moveFromLabel: "Cuenta desde la que se realiza el traslado:"
   moveFromDescription: "Si quieres transferir seguidores de otra cuenta a esta cuenta y trasladarlos, tendrás que crear un alias aquí. Asegúrate de crearlo antes de realizar el traslado. Introduce la cuenta desde la que estás moviendo los seguidores así: @person@instance.com"
+  moveTo: "Mover esta cuenta a una nueva"
+  moveToLabel: "Cuenta destino:"
+  moveAccountDescription: "Esta operación no puede deshacerse. En primer lugar, asegúrese de haber creado un alias para esta cuenta en la cuenta a la que se va a trasladar. Después de crear el alias, introduzca la cuenta a la que se está trasladando de la siguiente manera: @person@instance.com"
   migrationConfirm: "¿Estás seguro de que quieres mover esta cuenta a {account}? Una vez trasladada, no podrás deshacer el traslado y no podrás volver a utilizar la cuenta original.\n\nAdemás, compruebe que ha configurado un alias en el destino del traslado."
+  movedTo: "Cuenta destino:"
 _achievements:
   earnedAt: "Desbloqueado el"
   _types:
@@ -1523,32 +1526,6 @@ _time:
   minute: "Minutos"
   hour: "Horas"
   day: "Días"
-_tutorial:
-  title: "Cómo usar Misskey"
-  step1_1: "Bienvenido"
-  step1_2: "Esta imagen se llama \"Linea de tiempo\" y muestra en orden cronológico las \"notas\" tuyas y de la gente que \"sigues\""
-  step1_3: "Si no estás escribiendo ninguna nota y no estás siguiendo a nadie, es esperable que no se muestre nada en la linea de tiempo"
-  step2_1: "Antes de crear notas y seguir a alguien, primero vamos a crear tu perfil"
-  step2_2: "Si provees información sobre quien eres, será más fácil para que otros usuarios te sigan"
-  step3_1: "¿Has podido crear tu perfil sin problemas?"
-  step3_2: "Con esto, prueba hacer una nota. Aprieta el botón con forma de lápiz que está arriba de la imagen y abre el formulario."
-  step3_3: "Si has escrito el contenido, aprieta el botón que está arriba a la derecha del formulario para postear."
-  step3_4: "¿No se te ocurre un contenido? Prueba con decir \"Empecé a usar Misskey\""
-  step4_1: "¿Has posteado?"
-  step4_2: "Si tu nota puede verse en la linea de tiempo, fue todo un éxito."
-  step5_1: "Luego, ponte a seguir a otra gente y haz que tu linea de tiempo esté más animada."
-  step5_2: "Puedes ver las notas destacadas en {featured} y desde allí seguir a usuarios que te importan. También puedes buscar usuario destacados en {explore}."
-  step5_3: "Para seguir a un usuario, haz click en su avatar para ver su página de usuario y allí apretar el botón \"seguir\""
-  step5_4: "De esa manera, puede pasar un tiempo hasta que el usuario apruebe al seguidor."
-  step6_1: "Si puedes ver en la linea de tiempo las notas de otros usuarios, fue todo un éxito."
-  step6_2: "En las notas de otros usuarios puedes añadir una \"reacción\", para poder responder rápidamente."
-  step6_3: "Para añadir una reacción, haz click en el botón \"+\" de la nota y elige la reacción que prefieras."
-  step7_1: "Así terminó la explicación del funcionamiento básico de Misskey. Eso fue todo."
-  step7_2: "Si quieres conocer más sobre Misskey, prueba con la sección {help}."
-  step7_3: "Así, disfruta de Misskey 🚀"
-  step8_1: "Por último, ¿por qué no activar las notificaciones emergentes?"
-  step8_2: "Al recibir notificaciones emergentes, estarás al tanto de reacciones, seguimientos y menciones incluso cuando Misskey no esté abierto."
-  step8_3: "La configuración de las notificaciones puede modificarse posteriormente."
 _2fa:
   alreadyRegistered: "Ya has completado la configuración."
   registerTOTP: "Registrar aplicación autenticadora"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index e0767fdc09830cb44e5947e18128f05b9e5fae9f..e98ee9ff9befa993963d961904f620ff64c826a9 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -20,6 +20,7 @@ noNotes: "Aucune note"
 noNotifications: "Aucune notification"
 instance: "Instance"
 settings: "Paramètres"
+notificationSettings: "Paramètres des notifications "
 basicSettings: "Paramètres généraux"
 otherSettings: "Paramètres avancés"
 openInWindow: "Ouvrir dans une nouvelle fenêtre"
@@ -259,12 +260,12 @@ noMoreHistory: "Il n’y a plus d’historique"
 startMessaging: "Commencer à discuter"
 nUsersRead: "Lu par {n} personnes"
 agreeTo: "J’accepte {0}"
-tos: "les conditions d’utilisation"
 start: "Commencer"
 home: "Principal"
 remoteUserCaution: "Les informations de ce compte risqueraient d’être incomplètes du fait que l’utilisateur·rice provient d’une instance distante."
 activity: "Activité"
 images: "Images"
+image: "Images"
 birthday: "Date de naissance"
 yearsOld: "{age} ans"
 registeredDate: "Inscrit le"
@@ -838,13 +839,16 @@ tenMinutes: "10 minutes"
 oneHour: "1 heure"
 oneDay: "1 jour"
 oneWeek: "1 semaine"
+oneMonth: "Un mois"
 reflectMayTakeTime: "Cela peut prendre un certain temps avant que cela ne se termine."
 failedToFetchAccountInformation: "Impossible de récupérer les informations du compte."
 rateLimitExceeded: "Limite de taux dépassée"
 cropImage: "Recadrer l'image"
 cropImageAsk: "Voulez-vous recadrer cette image ?"
+cropYes: "Rogner"
 file: "Fichiers"
 recentNHours: "Dernières {n} heures"
+recentNDays: "Derniers {n} jours"
 noEmailServerWarning: "Serveur de courrier non configuré."
 thereIsUnresolvedAbuseReportWarning: "Il n’y a aucun rapport non résolu."
 recommended: "Recommandé"
@@ -900,11 +904,47 @@ numberOfLikes: "Favoris"
 show: "Affichage"
 neverShow: "Ne plus afficher"
 remindMeLater: "Peut-être plus tard"
+roles: "Rôles"
+role: "Rôles"
+normalUser: "Simple utilisateur·rice"
+assign: "Attribuer"
 color: "Couleur"
+manageCustomEmojis: "Gestion des émojis personnalisés"
+preset: "Préréglage"
+selectFromPresets: "Sélectionner à partir des préréglages"
+license: "Licence"
+video: "Vidéo"
+videos: "Vidéos"
+dataSaver: "Économiseur de données"
+accountMigration: "Migration de compte"
+accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
+notificationDisplay: "Style des notifications"
+leftTop: "En haut à gauche"
+rightTop: "En haut à droite"
+leftBottom: "En bas à gauche"
+rightBottom: "En bas à droite"
+vertical: "Vertical"
+horizontal: "Latéral"
+youFollowing: "Abonné·e"
 _achievements:
   _types:
+    _notes1:
+      description: "Publiez votre première note"
+      flavor: "Passez un bon moment avec Misskey !"
     _notes100000:
       title: "ALL YOUR NOTE ARE BELONG TO US"
+    _login3:
+      description: "Se connecter pour un total de 3 jours"
+    _login7:
+      description: "Se connecter pour un total de 7 jours"
+    _login15:
+      description: "Se connecter pour un total de 15 jours"
+    _login30:
+      description: "Se connecter pour un total de 30 jours"
+    _login60:
+      description: "Se connecter pour un total de 60 jours"
+    _login100:
+      description: "Se connecter pour un total de 100 jours"
     _login1000:
       flavor: "Merci d'utiliser Misskey !"
     _markedAsCat:
@@ -912,12 +952,21 @@ _achievements:
       flavor: "Je n'ai pas encore de nom"
     _following50:
       title: "Beaucoup d'amis"
+    _followers10:
+      title: "Abonnez-moi !"
+    _viewInstanceChart:
+      title: "Analyste"
+    _loggedInOnNewYearsDay:
+      title: "Bonne année !"
 _role:
+  assignTarget: "Attribuer"
   priority: "Priorité"
   _priority:
     low: "Basse"
     middle: "Moyen"
     high: "Haute"
+  _options:
+    canManageCustomEmojis: "Gestion des émojis personnalisés"
 _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"
@@ -1141,31 +1190,6 @@ _time:
   minute: "min"
   hour: "h"
   day: "j"
-_tutorial:
-  title: "Comment utiliser Misskey"
-  step1_1: "Bienvenue,"
-  step1_2: "Cette page est appelée « un fil ». Elle affiche les « notes » des personnes auxquelles vous êtes abonné dans un ordre chronologique."
-  step1_3: "Votre fil est actuellement vide vu que vous ne suivez aucun compte et que vous n’avez publié aucune note, pour l’instant."
-  step2_1: "Procédons d’abord à la préparation de votre profil avant d’écrire une note et/ou de vous abonner à un compte."
-  step2_2: "En fournissant quelques informations sur vous, il sera plus facile pour les autres de s’abonner à votre compte."
-  step3_1: "Vous avez fini de créer votre profil ?"
-  step3_2: "L’étape suivante consiste à créer une note. Vous pouvez commencer en cliquant sur l’icône crayon sur l’écran."
-  step3_3: "Remplissez le cadran et cliquez sur le bouton en haut à droite pour envoyer."
-  step3_4: "Vous n’avez rien à dire ? Essayez d’écrire « J’ai commencé à utiliser Misskey »."
-  step4_1: "Avez-vous publié votre première note ?"
-  step4_2: "Youpi ! Celle-ci est maintenant affichée sur votre fil d’actualité."
-  step5_1: "Maintenant, essayons de nous abonner à d’autres personnes afin de rendre votre fil plus vivant."
-  step5_2: "La page {featured} affiche les notes en tendance sur la présente instance et {explore} vous permet de trouver des utilisateur·rice·s en tendance. Essayez de vous abonner aux gens que vous aimez !"
-  step5_3: "Pour pouvoir suivre d’autres utilisateur·rice, cliquez sur leur avatar afin d’afficher la page du profil utilisateur ensuite appuyez sur le bouton « S’abonner »."
-  step5_4: "Si l’autre utilisateur possède une icône sous forme d’un cadenas à côté de son nom, il devra accepter votre demande d’abonnement manuellement."
-  step6_1: "Maintenant, vous êtes en mesure de voir s’afficher les notes des autres utilisateur·rice·s sur votre propre fil."
-  step6_2: "Vous avez également la possibilité d’intéragir rapidement avec les notes des autres utilisateur·rice·s en ajoutant des « réactions »."
-  step6_3: "Pour ajouter une réaction à une note, cliquez sur le signe « + » de celle-ci et sélectionnez l’émoji souhaité."
-  step7_1: "Félicitations ! Vous avez atteint la fin du tutoriel de base pour l’utilisation de Misskey."
-  step7_2: "Si vous désirez en savoir plus sur Misskey, jetez un œil sur la section {help}."
-  step7_3: "Bon courage et amusez-vous bien sur Misskey ! 🚀"
-  step8_1: "Enfin, souhaitez-vous activer les notifications push ?"
-  step8_2: "En les activant, vous recevrez des notifications pour les mentions, les réactions, les suivis, etc., même lorsque Misskey n'est pas ouvert."
 _2fa:
   alreadyRegistered: "Configuration déjà achevée."
   step1: "Tout d'abord, installez une application d'authentification, telle que {a} ou {b}, sur votre appareil."
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 97c0b02fe55d408a512515011c392cf92ced3b65..5159775fc440434bbd2fbce4b4c3eb26bf9b8edf 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -263,12 +263,12 @@ nUsersRead: "Dibaca oleh {n}"
 agreeTo: "Saya setuju kepada {0}"
 agreeBelow: "Saya setuju dengan di bawah ini"
 basicNotesBeforeCreateAccount: "Catatan penting"
-tos: "Syarat dan ketentuan"
 start: "Mulai"
 home: "Beranda"
 remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal dari instansi luar."
 activity: "Aktivitas"
 images: "Gambar"
+image: "Gambar"
 birthday: "Tanggal lahir"
 yearsOld: "{age} tahun"
 registeredDate: "Bergabung pada"
@@ -953,6 +953,8 @@ letsLookAtTimeline: "LIhat timeline"
 disableFederationConfirm: "Matikan federasi?"
 disableFederationConfirmWarn: "Mematikan federasi tidak membuat kiriman menjadi privat. Umumnya, mematikan federasi tidak diperlukan."
 disableFederationOk: "Matikan federasi"
+horizontal: "Horisontal"
+youFollowing: "Mengikuti"
 _achievements:
   earnedAt: "Terbuka pada"
   _types:
@@ -1472,32 +1474,6 @@ _time:
   minute: "menit"
   hour: "jam"
   day: "hari"
-_tutorial:
-  title: "Cara menggunakan Misskey"
-  step1_1: "Selamat datang!"
-  step1_2: "Halaman ini disebut \"linimasa\". Halaman ini menampilkan \"catatan\" yang diurutkan secara kronologis dari orang-orang yang kamu \"ikuti\"."
-  step1_3: "Linimasa kamu kosong, karena kamu belum mencatat catatan apapun atau mengikuti siapapun."
-  step2_1: "Selesaikan menyetel profilmu sebelum menulis sebuah catatan atau mengikuti seseorang."
-  step2_2: "Menyediakan beberapa informasi tentang siapa kamu akan membuat orang lain mudah untuk mengikutimu kembali."
-  step3_1: "Selesai menyetel profil kamu?"
-  step3_2: "Langkah selanjutnya adalah membuat catatan. Kamu bisa lakukan ini dengan mengklik ikon pensil pada layar kamu."
-  step3_3: "Isilah di dalam modal dan tekan tombol pada atas kanan untuk memcatat catatan kamu."
-  step3_4: "Bingung tidak berpikiran untuk mengatakan sesuatu? Coba saja \"baru aja ikutan bikin akun misskey punyaku\"!"
-  step4_1: "Selesai mencatat catatan pertamamu?"
-  step4_2: "Horee! Sekarang catatan pertamamu sudah ditampilkan di linimasa milikmu."
-  step5_1: "Sekarang, mari mencoba untuk membuat linimasamu lebih hidup dengan mengikuti orang lain."
-  step5_2: "{featured} akan memperlihatkan catatan yang sedang tren saat ini untuk kamu. {explore} akan membantumu untuk mencari pengguna yang sedang tren juga saat ini. Coba ikuti seseorang yang kamu suka!"
-  step5_3: "Untuk mengikuti pengguna lain, klik pada ikon mereka dan tekan tombol follow pada profil mereka."
-  step5_4: "Jika pengguna lain memiliki ikon gembok di sebelah nama mereka, maka pengguna rersebut harus menyetujui permintaan mengikuti dari kamu secara manual."
-  step6_1: "Sekarang kamu dapat melihat catatan pengguna lain pada linimasamu."
-  step6_2: "Kamu juga bisa memberikan \"reaksi\" ke catatan orang lain untuk merespon dengan cepat."
-  step6_3: "Untuk memberikan \"reaksi\", tekan tanda \"+\" pada catatan pengguna lain dan pilih emoji yang kamu suka untuk memberikan reaksimu kepada mereka."
-  step7_1: "Yay, Selamat! Kamu sudah menyelesaikan tutorial dasar Misskey."
-  step7_2: "Jika kamu ingin mempelajari lebih lanjut tentang Misskey, cobalah berkunjung ke bagian {help}."
-  step7_3: "Semoga berhasil dan bersenang-senanglah! 🚀"
-  step8_1: "Yang terakhir, apakah kamu ingin menyalakam pemberitahuan push?"
-  step8_2: "Menyalakan ini akan memungkinkan kamu menerima pemberitahuan untuk sebutan, reaksi, ikuti, dll. Bahkan ketika Misskey sedang tidak dibuka."
-  step8_3: "Kamu dapat mengganti pengaturan ini nanti."
 _2fa:
   alreadyRegistered: "Kamu telah mendaftarkan perangkat otentikasi dua faktor."
   step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu."
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index f43adcb35bed0f4a194d6b92edfcfdf9a1ba5a63..cacd91cac940e14a2b30825ba53c246d0cfc695a 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -263,14 +263,16 @@ noMoreHistory: "Non c'è più cronologia da visualizzare"
 startMessaging: "Nuovo messaggio"
 nUsersRead: "Letto da {n} persone"
 agreeTo: "Sono d'accordo con {0}"
+agree: "D'accordo"
 agreeBelow: "Accetto quanto riportato sotto"
 basicNotesBeforeCreateAccount: "Note importanti"
-tos: "Termini di servizio"
+termsOfService: "Informativa Privacy"
 start: "Inizia!"
 home: "Home"
 remoteUserCaution: "Può darsi che le informazioni siano incomplete perché questo è un utente remoto."
 activity: "Attività"
 images: "Immagini"
+image: "Immagini"
 birthday: "Compleanno"
 yearsOld: "{age} anni"
 registeredDate: "Iscrizione a.."
@@ -474,6 +476,8 @@ createAccount: "Crea il tuo profilo"
 existingAccount: "Profilo esistente"
 regenerate: "Generare di nuovo"
 fontSize: "Dimensione carattere"
+mediaListWithOneImageAppearance: "Altezza dell'elenco media con una sola immagine "
+limitTo: "Limita a {x}"
 noFollowRequests: "Non hai alcuna richiesta di follow"
 openImageInNewTab: "Apri le immagini in un nuovo tab"
 dashboard: "Pannello di controllo"
@@ -947,6 +951,10 @@ manageCustomEmojis: "Gestisci le emoji personalizzate"
 youCannotCreateAnymore: "Non puoi creare, hai raggiunto il limite."
 cannotPerformTemporary: "Indisponibilità temporanea"
 cannotPerformTemporaryDescription: "L'attività non può essere svolta, poiché si è raggiunto il limite di esecuzioni possibili. Per favore, riprova più tardi."
+invalidParamError: "Errore, parametro non valido"
+invalidParamErrorDescription: "Riscontrato un errore nei parametri della richiesta. Solitamente potrebbe essere un malfunzionamento, ma a volte potrebbe essere causato dalla quantità eccessiva di caratteri nel parametro."
+permissionDeniedError: "Errore, attività non autorizzata"
+permissionDeniedErrorDescription: "Non si dispone dell'autorizzazione per eseguire questa operazione."
 preset: "Preimpostato"
 selectFromPresets: "Seleziona preimpostato"
 achievements: "Obiettivi raggiunti"
@@ -970,7 +978,7 @@ invitationRequiredToRegister: "L'accesso a questa istanza è solo ad invito. Pu
 emailNotSupported: "L'istanza non supporta l'invio di email"
 postToTheChannel: "Pubblica nel canale"
 cannotBeChangedLater: "Non sarà più modificabile"
-reactionAcceptance: "Accettazione reazioni"
+reactionAcceptance: "Reazioni consentite"
 likeOnly: "Solo i Like"
 likeOnlyForRemote: "Solo Like remoti"
 rolesAssignedToMe: "I miei ruoli"
@@ -990,17 +998,39 @@ enableChartsForFederatedInstances: "Abilita i grafici per le istanze federate"
 showClipButtonInNoteFooter: "Aggiungi il bottone Clip tra le azioni delle Note"
 largeNoteReactions: "Ingrandisci le reazioni"
 noteIdOrUrl: "ID della Nota o URL"
+video: "Video"
+videos: "Video"
+dataSaver: "Risparmia dati"
 accountMigration: "Migrazione del profilo"
 accountMoved: "Questo profilo ha migrato altrove:"
 forceShowAds: "Mostra sempre i banner"
+addMemo: "Aggiungi Memo"
+editMemo: "Modifica Memo"
+notificationDisplay: "Stile delle notifiche"
+leftTop: "In alto a sinistra"
+rightTop: "In alto a destra"
+leftBottom: "In basso a sinistra"
+rightBottom: "In basso a destra"
+stackAxis: "Allineamento"
+vertical: "Verticale"
+horizontal: "Laterale"
+position: "Posizione"
+serverRules: "Regolamento"
+pleaseConfirmBelowBeforeSignup: "Ai sensi del regolamento EU 679/2016 GDPR, autorizzo il trattamento dati personali come descritto nella informativa Privacy."
+pleaseAgreeAllToContinue: "Per continuare, occorre selezionare ed essere d'accordo su tutto."
+continue: "Continua"
+youFollowing: "Seguiti"
+_serverRules:
+  description: "In Europa è necessario mostrare l'informativa sul trattamento dei dati personali, prima della registrazione al servizio."
 _accountMigration:
-  moveTo: "Migrare questo profilo verso un un altro"
-  moveToLabel: "Profilo verso cui migrare"
-  moveAccountDescription: "Questa attività è irreversibile! Innanzitutto, assicurati di aver creato, nella istanza di destinazione, un alias con l'indirizzo di questo profilo. Successivamente, indica qui il profilo di destinazione in questo modo: @persona@istanza.it"
   moveFrom: "Migra un altro profilo dentro a questo"
   moveFromLabel: "Profilo da cui migrare:"
   moveFromDescription: "Se desideri spostare i profili follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@istanza.it"
+  moveTo: "Migrare questo profilo verso un un altro"
+  moveToLabel: "Profilo verso cui migrare"
+  moveAccountDescription: "Questa attività è irreversibile! Innanzitutto, assicurati di aver creato, nella istanza di destinazione, un alias con l'indirizzo di questo profilo. Successivamente, indica qui il profilo di destinazione in questo modo: @persona@istanza.it"
   migrationConfirm: "Vuoi davvero migrare questo profilo su {account}? L'azione è irreversibile e non potrai più utilizzare questo profilo nel suo stato originale.\nInoltre, assicurati di aver già creato un alias sull'account a cui ti stai trasferendo."
+  movedTo: "Profilo verso cui migrare"
 _achievements:
   earnedAt: "Data di conseguimento"
   _types:
@@ -1172,6 +1202,9 @@ _achievements:
     _client30min:
       title: "Piccola grande pausa"
       description: "Hai passato più di 30 minuti su Misskey"
+    _client60min:
+      title: "Misskey negli occhi"
+      description: "Hai letto Misskey almeno per un'ora"
     _noteDeletedWithin1min:
       title: "Ooops!"
       description: "Hai eliminato una nota entro un minuto dalla sua pubblicazione"
@@ -1260,6 +1293,8 @@ _role:
   iconUrl: "URL dell'icona"
   asBadge: "Mostra come badge"
   descriptionOfAsBadge: "Se indicato, accanto al nome utente viene visualizzata l'icona del ruolo."
+  isExplorable: "La timeline del ruolo è pubblica"
+  descriptionOfIsExplorable: "Selezionandolo, la timeline del ruolo diventerà accessibile pubblicamente. Tranne se il ruolo non è pubblico."
   displayOrder: "Ordine di visualizzazione"
   descriptionOfDisplayOrder: "I valori più alti vengono visualizzati per primi"
   canEditMembersByModerator: "Anche i Moderatori assegnano profili a questo ruolo"
@@ -1527,32 +1562,6 @@ _time:
   minute: "min"
   hour: "ore"
   day: "giorni"
-_tutorial:
-  title: "Come usare Misskey"
-  step1_1: "Eccoci!"
-  step1_2: "Questa pagina si chiama \"Timeline \" e mostra in ordine cronologico le \"note\" delle persone che segui."
-  step1_3: "Attualmente la tua Timeline è vuota perché non segui alcun profilo e non hai ancora pubblicato alcuna nota."
-  step2_1: "Prima di scrivere una «Nota» o di seguire altri profili, prepara il tuo profilo!"
-  step2_2: "Se aggiungi informazioni personali aumenterai le tue possibilità di essere seguit@ da altre persone. "
-  step3_1: "Hai finito di impostare il tuo profilo?"
-  step3_2: "Ora puoi pubblicare una «Nota». Proviamo subito! Premi il bottone con l'icona «penna» per iniziare a scrivere in una finestra di dialogo.  "
-  step3_3: "Scritto il testo della nota, puoi pubblicarla premendo il pulsante nella parte superiore destra della finestra di dialogo."
-  step3_4: "Non ti viene niente in mente? Perché non scrivi semplicemente \"Ho appena iniziato a usare Misskey\"?"
-  step4_1: "Hai pubblicato qualcosa?"
-  step4_2: "Se puoi visualizzare la tua nota sulla timeline, ce l'hai fatta!"
-  step5_1: "Adesso, cerca di seguire altre persone per vivacizzare la tua timeline. "
-  step5_2: "La pagina {featured} mostra le note di tendenza su questa istanza, magari ti aiuterà a trovare profili che ti piacciono e che vorrai seguire. Altrimenti potrai trovare utenti popolari anche usando {explore}."
-  step5_3: "Clicca l'immagine per aprire il profilo e premere il bottone \"Seguire\""
-  step5_4: "Se l'altro profilo ha un lucchetto vicino al nome, significa che occorre un po' di tempo prima che approvi manualmente la tua richiesta di follow."
-  step6_1: "Adesso, dovresti essere in grado di vedere le note dagli altri profili sulla tua timeline."
-  step6_2: "Puoi anche rispondere alle note con un click, scegliendo le reazioni immediate."
-  step6_3: "Per inviare una reazione, premi l'icona + della nota e scegli l'emoji che vuoi mandare."
-  step7_1: "Congratulazioni! Hai completato l'esercitazione iniziale su come usare Misskey."
-  step7_2: "Se vuoi saperne di più su Misskey, puoi dare un'occhiata alla sezione {help}."
-  step7_3: "Da ultimo, buon divertimento su Misskey! 🚀"
-  step8_1: "Per concludere, vuoi attivare le notifiche push?"
-  step8_2: "Attivandole, otterrai notifiche di follow, reazioni e menzioni anche quando Misskey è chiuso."
-  step8_3: "Potrai modificare questa impostazione."
 _2fa:
   alreadyRegistered: "La configurazione è stata già completata."
   registerTOTP: "Registra un'app di autenticazione"
@@ -1890,6 +1899,7 @@ _deck:
     channel: "Canale"
     mentions: "Menzioni"
     direct: "Diretta"
+    roleTimeline: "Timeline Ruolo"
 _dialog:
   charactersExceeded: "Hai superato il limite di {max} caratteri! ({corrente})"
   charactersBelow: "Sei al di sotto del minimo di {min} caratteri!  ({corrente})"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 092a4aed325ad5d961c1279311fb3e3a0722a3cf..8bbf9459f58d5fe0f372f0597c1b07c0f57f7db3 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -263,14 +263,16 @@ noMoreHistory: "これより過去の履歴はありません"
 startMessaging: "チャットを開始"
 nUsersRead: "{n}人が読みました"
 agreeTo: "{0}に同意"
+agree: "同意する"
 agreeBelow: "下記に同意する"
 basicNotesBeforeCreateAccount: "基本的な注意事項"
-tos: "利用規約"
+termsOfService: "利用規約"
 start: "始める"
 home: "ホーム"
 remoteUserCaution: "リモートユーザーのため、情報が不完全です。"
 activity: "アクティビティ"
 images: "画像"
+image: "画像"
 birthday: "誕生日"
 yearsOld: "{age}æ­³"
 registeredDate: "登録日"
@@ -474,6 +476,8 @@ createAccount: "アカウントを作成"
 existingAccount: "既存のアカウント"
 regenerate: "再生成"
 fontSize: "フォントサイズ"
+mediaListWithOneImageAppearance: "画像が1枚のみのメディアリストの高さ"
+limitTo: "{x}を上限に"
 noFollowRequests: "フォロー申請はありません"
 openImageInNewTab: "画像を新しいタブで開く"
 dashboard: "ダッシュボード"
@@ -556,6 +560,7 @@ accountDeletedDescription: "このアカウントは削除されています。"
 menu: "メニュー"
 divider: "分割線"
 addItem: "項目を追加"
+rearrange: "並び替え"
 relays: "リレー"
 addRelay: "リレーの追加"
 inboxUrl: "inboxのURL"
@@ -699,6 +704,8 @@ contact: "連絡先"
 useSystemFont: "システムのデフォルトのフォントを使う"
 clips: "クリップ"
 experimentalFeatures: "実験的機能"
+experimental: "実験的"
+thisIsExperimentalFeature: "これは実験的な機能です。仕様が変更されたり、正常に動作しなかったりする可能性があります。"
 developer: "開発者"
 makeExplorable: "アカウントを見つけやすくする"
 makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らなくなります。"
@@ -905,6 +912,7 @@ remoteOnly: "リモートのみ"
 failedToUpload: "アップロード失敗"
 cannotUploadBecauseInappropriate: "不適切な内容を含む可能性があると判定されたためアップロードできません。"
 cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いためアップロードできません。"
+cannotUploadBecauseExceedsFileSizeLimit: "ファイルサイズの制限を超えているためアップロードできません。"
 beta: "ベータ"
 enableAutoSensitive: "自動NSFW判定"
 enableAutoSensitiveDescription: "利用可能な場合は、機械学習を利用して自動でメディアにNSFWフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。"
@@ -938,6 +946,7 @@ didYouLikeMisskey: "Misskeyを気に入っていただけましたか?"
 pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
 roles: "ロール"
 role: "ロール"
+noRole: "ロールはありません"
 normalUser: "一般ユーザー"
 undefined: "未定義"
 assign: "アサイン"
@@ -947,6 +956,10 @@ manageCustomEmojis: "カスタム絵文字の管理"
 youCannotCreateAnymore: "これ以上作成することはできません。"
 cannotPerformTemporary: "一時的に利用できません"
 cannotPerformTemporaryDescription: "操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。"
+invalidParamError: "パラメータエラー"
+invalidParamErrorDescription: "リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。"
+permissionDeniedError: "操作が拒否されました"
+permissionDeniedErrorDescription: "このアカウントにはこの操作を行うための権限がありません。"
 preset: "プリセット"
 selectFromPresets: "プリセットから選択"
 achievements: "実績"
@@ -990,18 +1003,74 @@ enableChartsForFederatedInstances: "リモートサーバーのチャートを
 showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
 largeNoteReactions: "ノートのリアクションを大きく表示"
 noteIdOrUrl: "ノートIDまたはURL"
-accountMigration: "アカウントの引っ越し"
-accountMoved: "このユーザーは新しいアカウントに引っ越しました:"
+video: "å‹•ç”»"
+videos: "å‹•ç”»"
+dataSaver: "データセーバー"
+accountMigration: "アカウントの移行"
+accountMoved: "このユーザーは新しいアカウントに移行しました:"
+accountMovedShort: "このアカウントは移行されています"
+operationForbidden: "この操作はできません"
 forceShowAds: "常に広告を表示する"
+addMemo: "メモを追加"
+editMemo: "メモを編集"
+reactionsList: "リアクション一覧"
+renotesList: "Renote一覧"
+notificationDisplay: "通知の表示"
+leftTop: "左上"
+rightTop: "右上"
+leftBottom: "左下"
+rightBottom: "右下"
+stackAxis: "スタック方向"
+vertical: "縦"
+horizontal: "横"
+position: "位置"
+serverRules: "サーバールール"
+pleaseConfirmBelowBeforeSignup: "このサーバーに登録する前に、以下を確認してください。"
+pleaseAgreeAllToContinue: "続けるには、全ての「同意する」にチェックが入っている必要があります。"
+continue: "続ける"
+preservedUsernames: "予約ユーザー名"
+preservedUsernamesDescription: "予約するユーザー名を改行で列挙します。ここで指定されたユーザー名はアカウント作成時に使えなくなりますが、管理者によるアカウント作成時はこの制限を受けません。また、既に存在するアカウントも影響を受けません。"
+createNoteFromTheFile: "このファイルからノートを作成"
+archive: "アーカイブ"
+channelArchiveConfirmTitle: "{name}をアーカイブしますか?"
+channelArchiveConfirmDescription: "アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。"
+thisChannelArchived: "このチャンネルはアーカイブされています。"
+displayOfNote: "ノートの表示"
+initialAccountSetting: "初期設定"
+youFollowing: "フォロー中"
+
+_initialAccountSetting:
+  accountCreated: "アカウントの作成が完了しました!"
+  letsStartAccountSetup: "アカウントの初期設定を行いましょう。"
+  letsFillYourProfile: "まずはあなたのプロフィールを設定しましょう。"
+  profileSetting: "プロフィール設定"
+  theseSettingsCanEditLater: "これらの設定は後から変更できます。"
+  youCanEditMoreSettingsInSettingsPageLater: "この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。"
+  followUsers: "タイムラインを構築するため、気になるユーザーをフォローしてみましょう。"
+  pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。"
+  initialAccountSettingCompleted: "初期設定が完了しました!"
+  haveFun: "{name}をお楽しみください!"
+  ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
+  skipAreYouSure: "初期設定をスキップしますか?"
+
+_serverRules:
+  description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
 
 _accountMigration:
-  moveTo: "このアカウントを新しいアカウントに引っ越す"
-  moveToLabel: "引っ越し先のアカウント:"
-  moveAccountDescription: "この操作は取り消せません。まずは引っ越し先のアカウントでこのアカウントに対しエイリアスを作成したことを確認してください。エイリアス作成後、引っ越し先のアカウントをこのように入力してください:@person@instance.com"
-  moveFrom: "別のアカウントからこのアカウントに引っ越す"
-  moveFromLabel: "引っ越し元のアカウント:"
-  moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。必ず引っ越しを実行する前に作成してください!引っ越し元のアカウントをこのように入力してください:@person@instance.com"
-  migrationConfirm: "本当にこのアカウントを {account} に引っ越しますか?一度引っ越しを行うと取り消せず、二度とこのアカウントを元の状態で使用できなくなります。\nまた、引っ越し先のアカウントでエイリアスを作成したことを確認してください。"
+  moveFrom: "別のアカウントからこのアカウントに移行"
+  moveFromSub: "別のアカウントへエイリアスを作成"
+  moveFromLabel: "移行元のアカウント #{n}"
+  moveFromDescription: "別のアカウントからこのアカウントに移行したい場合、ここでエイリアスを作成しておく必要があります。\n移行元のアカウントをこのように入力してください: @username@server.example.com\n削除するには、入力欄を空にして保存します(非推奨)。"
+  moveTo: "このアカウントを新しいアカウントへ移行"
+  moveToLabel: "移行先のアカウント:"
+  moveCannotBeUndone: "アカウントを移行すると、取り消すことはできません。"
+  moveAccountDescription: "新しいアカウントへ移行します。\n ・フォロワーが新しいアカウントを自動でフォローします\n ・このアカウントからのフォローは全て解除されます\n ・このアカウントではノートの作成などができなくなります\n\nフォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。\nリスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。\n\n(この説明はこのサーバー(Misskey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。)"
+  moveAccountHowTo: "アカウントの移行には、まずは移行先のアカウントでこのアカウントに対しエイリアスを作成します。\nエイリアス作成後、移行先のアカウントを次のように入力してください: @username@server.example.com"
+  startMigration: "移行する"
+  migrationConfirm: "本当にこのアカウントを {account} に移行しますか?一度移行すると取り消せず、二度とこのアカウントを元の状態で使用できなくなります。"
+  movedAndCannotBeUndone: "\nアカウントは移行されています。\n移行を取り消すことはできません。"
+  postMigrationNote: "このアカウントからのフォロー解除は移行操作から24時間後に実行されます。\nこのアカウントのフォロー・フォロワー数は0になっています。フォロワーの解除はされないため、あなたのフォロワーはこのアカウントのフォロワー向け投稿を引き続き閲覧できます。"
+  movedTo: "移行先のアカウント:"
 
 _achievements:
   earnedAt: "獲得日時"
@@ -1174,6 +1243,9 @@ _achievements:
     _client30min:
       title: "ひとやすみ"
       description: "クライアントを起動してから30分以上経過した"
+    _client60min:
+      title: "Misskeyの見すぎ"
+      description: "クライアントを起動してから60分以上経過した"
     _noteDeletedWithin1min:
       title: "いまのなし"
       description: "投稿してから1分以内にその投稿を削除した"
@@ -1263,6 +1335,8 @@ _role:
   iconUrl: "アイコン画像のURL"
   asBadge: "バッジとして表示"
   descriptionOfAsBadge: "オンにすると、ユーザー名の横にロールのアイコンが表示されます。"
+  isExplorable: "ロールタイムラインを公開"
+  descriptionOfIsExplorable: "オンにすると、ロールのタイムラインを公開します。ロールの公開がオフの場合、タイムラインの公開はされません。"
   displayOrder: "表示順"
   descriptionOfDisplayOrder: "数値が大きいほどUI上で先頭に表示されます。"
   canEditMembersByModerator: "モデレーターのメンバー編集を許可"
@@ -1279,6 +1353,7 @@ _role:
     canInvite: "サーバー招待コードの発行"
     canManageCustomEmojis: "カスタム絵文字の管理"
     driveCapacity: "ドライブ容量"
+    alwaysMarkNsfw: "ファイルにNSFWを常に付与"
     pinMax: "ノートのピン留めの最大数"
     antennaMax: "アンテナの作成可能数"
     wordMuteMax: "ワードミュートの最大文字数"
@@ -1556,32 +1631,16 @@ _time:
   hour: "時間"
   day: "æ—¥"
 
-_tutorial:
+_timelineTutorial:
   title: "Misskeyの使い方"
-  step1_1: "ようこそ。"
-  step1_2: "この画面は「タイムライン」と呼ばれ、あなたや、あなたが「フォロー」する人の「ノート」が時系列で表示されます。"
-  step1_3: "あなたはまだ何もノートを投稿しておらず、誰もフォローしていないので、タイムラインには何も表示されていないはずです。"
-  step2_1: "ノートを作成したり誰かをフォローしたりする前に、まずあなたのプロフィールを完成させましょう。"
-  step2_2: "あなたがどんな人かわかると、多くの人にノートを見てもらえたり、フォローしてもらいやすくなります。"
-  step3_1: "プロフィール設定はうまくできましたか?"
-  step3_2: "では試しに、何かノートを投稿してみてください。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
-  step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できます。"
-  step3_4: "内容が思いつかない?「Misskey始めました」というのはいかがでしょう。"
-  step4_1: "投稿できましたか?"
-  step4_2: "あなたのノートがタイムラインに表示されていれば成功です。"
-  step5_1: "次は、他の人をフォローしてタイムラインを賑やかにしたいところです。"
-  step5_2: "{featured}で人気のノートが見れるので、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできます。"
-  step5_3: "ユーザーをフォローするには、ユーザーのアイコンをクリックしてユーザーページを表示し、「フォロー」ボタンを押します。"
-  step5_4: "ユーザーによっては、フォローが承認されるまで時間がかかる場合があります。"
-  step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功です。"
-  step6_2: "他の人のノートには、「リアクション」を付けることができ、簡単にあなたの反応を伝えられます。"
-  step6_3: "リアクションを付けるには、ノートの「+」マークをクリックして、好きなリアクションを選択します。"
-  step7_1: "これで、Misskeyの基本的な使い方の説明は終わりました。お疲れ様でした。"
-  step7_2: "もっとMisskeyについて知りたいときは、{help}を見てみてください。"
-  step7_3: "では、Misskeyをお楽しみください🚀"
-  step8_1: "最後に、プッシュ通知を有効化してみませんか?"
-  step8_2: "プッシュ通知を受け取ることで、Misskeyを開いていない時にもリアクションやフォロー、メンションなどに気づけます。"
-  step8_3: "通知の設定は後から変更できます。"
+  step1_1: "この画面は「タイムライン」です。{name}に投稿された「ノート」が時系列で表示されます。"
+  step1_2: "タイムラインにはいくつか種類があり、例えば「ホームタイムライン」にはあなたがフォローしている人のノートが流れ、「ローカルタイムライン」には{name}全体のノートが流れます。"
+  step2_1: "試しに、何かノートを投稿してみましょう。画面上にある鉛筆マークのボタンを押すとフォームが開きます。"
+  step2_2: "初めてのノートの内容は、あなたの自己紹介や「{name}始めました」などがおすすめです。"
+  step3_1: "投稿できましたか?"
+  step3_2: "あなたのノートがタイムラインに表示されていれば成功です。"
+  step4_1: "ノートには、「リアクション」を付けることができます。"
+  step4_2: "リアクションを付けるには、ノートの「+」マークをクリックして、好きな絵文字を選択します。"
 
 _2fa:
   alreadyRegistered: "既に設定は完了しています。"
@@ -1763,7 +1822,7 @@ _profile:
   metadataDescription: "プロフィールに表として追加情報を表示することができます。"
   metadataLabel: "ラベル"
   metadataContent: "内容"
-  changeAvatar: "アバター画像を変更"
+  changeAvatar: "アイコン画像を変更"
   changeBanner: "バナー画像を変更"
 
 _exportOrImport:
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 4117a25b0e914a8d480750cba9a9f4d7b6ce87ec..512cce145282afed64a3763b71b1befc1f01b646 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1,7 +1,7 @@
 ---
 _lang_: "日本語 (関西弁)"
 headlineMisskey: "ノートでつながるネットワーク"
-introMisskey: "ようお越し!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか🚀"
+introMisskey: "ようお越し!Misskeyは、オープンソースの分散型マイクロブログサービスやねん。\n「ノート」を作って、いま起こっとることを共有したり、あんたについて皆に発信しよう📡\n「ツッコミ」機能で、皆のノートに素早く反応を追加したりもできるで✌\nほな新しい世界を探検しよか🚀"
 poweredByMisskeyDescription: "{name}は、オープンソースのプラットフォーム<b>Misskey</b>のサーバーのひとつなんやで。"
 monthAndDay: "{month}月 {day}日"
 search: "探す"
@@ -26,7 +26,7 @@ otherSettings: "ほかの設定"
 openInWindow: "ウィンドウで開くで"
 profile: "プロフィール"
 timeline: "タイムライン"
-noAccountDescription: "自己紹介はあらへん"
+noAccountDescription: "自己紹介食ってもた"
 login: "ログイン"
 loggingIn: "ログインしよるで"
 logout: "ログアウト"
@@ -38,9 +38,9 @@ addUser: "ユーザーを追加や"
 favorite: "お気に入り"
 favorites: "お気に入り"
 unfavorite: "やっぱ気に入らん"
-favorited: "お気に入りに登録したで"
+favorited: "お気に入りに入れたで"
 alreadyFavorited: "もうお気に入りに入れとるがな。"
-cantFavorite: "アカン、お気に入り登録できへんかったで。"
+cantFavorite: "アカン、お気に入りに入れれんかったわ。"
 pin: "ピン留めしとく"
 unpin: "やっぱピン留めせん"
 copyContent: "内容をコピー"
@@ -61,7 +61,7 @@ youGotNewFollower: "フォローされたで"
 receiveFollowRequest: "フォローリクエストされたで"
 followRequestAccepted: "フォローが承認されたで"
 mention: "メンション"
-mentions: "うち宛て"
+mentions: "自分宛て"
 directNotes: "ダイレクト投稿"
 importAndExport: "インポートとエクスポート"
 import: "インポート"
@@ -109,12 +109,12 @@ inChannelQuote: "チャンネル内引用"
 pinnedNote: "ピン留めされとるノート"
 pinned: "ピン留めしとく"
 you: "あんた"
-clickToShow: "押したら出ら"
+clickToShow: "押したら見えるで"
 sensitive: "気いつけて見いや"
 add: "増やす"
-reaction: "リアクション"
-reactions: "リアクション"
-reactionSetting: "ピッカーに出しとくリアクション"
+reaction: "ツッコミ"
+reactions: "ツッコミ"
+reactionSetting: "ピッカーに出しとくツッコミ"
 reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。"
 rememberNoteVisibility: "公開範囲覚えといて"
 attachCancel: "のっけるのやめる"
@@ -263,14 +263,16 @@ noMoreHistory: "これより昔のんはあらへんで"
 startMessaging: "チャットやるで"
 nUsersRead: "{n}人が読んでもうた"
 agreeTo: "{0}に同意したで"
+agree: "せやな"
 agreeBelow: "下記に同意したる"
 basicNotesBeforeCreateAccount: "よう読んでやってや"
-tos: "利用規約"
+termsOfService: "使うための決め事"
 start: "始める"
 home: "ホーム"
 remoteUserCaution: "リモートユーザーやから、足りひん情報あるかもしれへん。"
 activity: "アクティビティ"
 images: "画像"
+image: "画像"
 birthday: "生まれた日"
 yearsOld: "{age}æ­³"
 registeredDate: "始めた日"
@@ -322,7 +324,7 @@ instanceName: "サーバー名"
 instanceDescription: "サーバーの紹介"
 maintainerName: "管理者はんの名前"
 maintainerEmail: "管理者はんのメールアドレス"
-tosUrl: "利用規約のURL"
+tosUrl: "使うための決め事のURL"
 thisYear: "今年"
 thisMonth: "今月"
 today: "今日"
@@ -460,7 +462,7 @@ uiLanguage: "UIの表示言語"
 aboutX: "{x}について"
 emojiStyle: "絵文字のスタイル"
 native: "ネイティブ"
-disableDrawer: "メニューをドロワーで表示せぇへん"
+disableDrawer: "メニューをドロワーで表示せえへん"
 showNoteActionsOnlyHover: "ノートの操作部をホバー時のみ表示するで"
 noHistory: "履歴はないわ。"
 signinHistory: "ログイン履歴"
@@ -474,6 +476,8 @@ createAccount: "アカウントを作るで"
 existingAccount: "前に作ったアカウント"
 regenerate: "もっぺん生成するで"
 fontSize: "字の大きさ"
+mediaListWithOneImageAppearance: "画像が1枚のみのメディアリストの高さ"
+limitTo: "{x}をいっぱいに"
 noFollowRequests: "フォロー申請はあらへんで"
 openImageInNewTab: "画像を新しいタブで開くで"
 dashboard: "ダッシュボード"
@@ -548,7 +552,7 @@ removeAllFollowingDescription: "{host}からのフォローをすべて解除す
 userSuspended: "このユーザーは...凍結されとる。"
 userSilenced: "このユーザーは...サイレンスされとる。"
 yourAccountSuspendedTitle: "あんたのアカウント凍結されとるで"
-yourAccountSuspendedDescription: "あんたのアカウントは、サーバーの利用規約に違反したとかの理由で、凍結されとるで。細かいことは管理者までお問い合わせたってなー。絶対に新しいアカウント作ったらあかんで。絶対やで。"
+yourAccountSuspendedDescription: "あんたのアカウントは、サーバーの使うための決め事に違反したとかの理由で、凍結されとるで。細かいことは管理者までお問い合わせたってなー。絶対に新しいアカウント作ったらあかんで。絶対やで。"
 tokenRevoked: "トークンが無効やで"
 tokenRevokedDescription: "ログイントークンが失効しとるで。もっかいログインしてもろてもええか?"
 accountDeleted: "アカウントは削除されとるで"
@@ -556,6 +560,7 @@ accountDeletedDescription: "このアカウントは削除されとるで。"
 menu: "メニュー"
 divider: "分割線"
 addItem: "項目を追加"
+rearrange: "並び替え"
 relays: "リレー"
 addRelay: "リレーの追加"
 inboxUrl: "inboxのURL"
@@ -582,7 +587,7 @@ preferencesBackups: "設定のバックアップ"
 deck: "デッキ"
 undeck: "デッキ解除"
 useBlurEffectForModal: "モーダルにぼかし効果を使用"
-useFullReactionPicker: "フル機能にリアクションピッカーを使用"
+useFullReactionPicker: "フル機能の突っ込みピッカーを使用"
 width: "å¹…"
 height: "高さ"
 large: "大"
@@ -675,8 +680,8 @@ repliedCount: "返信された数やで"
 renotedCount: "Renoteされた数やで"
 followingCount: "フォロー数やで"
 followersCount: "フォロワー数やで"
-sentReactionsCount: "リアクションした数やで"
-receivedReactionsCount: "リアクションされた数"
+sentReactionsCount: "ツッコんだ数"
+receivedReactionsCount: "ツッコまれた数"
 pollVotesCount: "アンケートに投票した数"
 pollVotedCount: "アンケートに投票された数"
 yes: "ええで"
@@ -699,6 +704,8 @@ contact: "連絡先"
 useSystemFont: "システムのデフォルトのフォントを使うで"
 clips: "クリップ"
 experimentalFeatures: "おためし機能やで"
+experimental: "実験的"
+thisIsExperimentalFeature: "これは実験的な機能やで。仕様が変更になったりちゃんと動かなかったりするかもやで。"
 developer: "開発者やで"
 makeExplorable: "アカウントを見つけやすくするで"
 makeExplorableDescription: "オフにすると、「みつける」にアカウントが載らんくなるで。"
@@ -745,7 +752,7 @@ receiveAnnouncementFromInstance: "サーバーからのお知らせを受け取
 emailNotification: "メール通知"
 publish: "公開"
 inChannelSearch: "チャンネル内検索"
-useReactionPickerForContextMenu: "右クリックでリアクションピッカーを開くようにする"
+useReactionPickerForContextMenu: "右クリックでツッコミピッカーを開くようにする"
 typingUsers: "{users}が今書きよるで"
 jumpToSpecifiedDate: "特定の日付にジャンプ"
 showingPastTimeline: "過去のタイムラインを表示してるで"
@@ -830,8 +837,8 @@ unread: "未読"
 filter: "フィルタ"
 controlPanel: "コントロールパネル"
 manageAccounts: "アカウントを管理"
-makeReactionsPublic: "リアクション一覧を公開するで"
-makeReactionsPublicDescription: "あんたがしたリアクション一覧を誰でも見れるようにするで。"
+makeReactionsPublic: "ツッコミ一覧を公開するで"
+makeReactionsPublicDescription: "あんたがしたツッコミ一覧を誰でも見れるようにするで。"
 classic: "クラシック"
 muteThread: "スレッドをミュート"
 unmuteThread: "スレッドのミュートを解除"
@@ -905,6 +912,7 @@ remoteOnly: "リモートのみ"
 failedToUpload: "アップロードに失敗してもうたわ…"
 cannotUploadBecauseInappropriate: "不適切な内容を含むかもしれへんって判定されたからアップロードできへんわ。"
 cannotUploadBecauseNoFreeSpace: "ドライブの空き容量が無いからアップロードできへんわ。"
+cannotUploadBecauseExceedsFileSizeLimit: "ファイルが思うたよりも大きいさかいアップロードできへんでこれ。"
 beta: "ベータ"
 enableAutoSensitive: "自動NSFW判定"
 enableAutoSensitiveDescription: "使える時は、機械学習を使って自動でメディアにNSFWフラグを設定するで。この機能をオフにしても、サーバーによっては自動で設定されることがあるで。"
@@ -938,6 +946,7 @@ didYouLikeMisskey: "Misskey気に入ってくれた?"
 pleaseDonate: "Misskeyは{host}が使用している無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
 roles: "ロール"
 role: "ロール"
+noRole: "ロールはありまへん"
 normalUser: "一般ユーザー"
 undefined: "未定義"
 assign: "アサイン"
@@ -947,6 +956,10 @@ manageCustomEmojis: "カスタム絵文字の管理"
 youCannotCreateAnymore: "これ以上作れなさそうやわ"
 cannotPerformTemporary: "一時的に利用できへんで"
 cannotPerformTemporaryDescription: "操作回数が制限を超えたから一時的に利用できへんくなったで。ちょっと時間置いてからもう一回やってやー。"
+invalidParamError: "パラメータがエラー言うとりますわ"
+invalidParamErrorDescription: "リクエストパラメータに問題があんねん。普通はバグやねんけど、もしかすると入力した文字数が多すぎるとかの可能性もあるから確認してや〜"
+permissionDeniedError: "操作が拒否されてもうた。"
+permissionDeniedErrorDescription: "自分のアカウントにはこの操作を行う権限があらへんねん"
 preset: "プリセット"
 selectFromPresets: "プリセットから選ぶ"
 achievements: "実績"
@@ -970,7 +983,7 @@ invitationRequiredToRegister: "今このサーバー招待制になってもう
 emailNotSupported: "このサーバーはメール配信がサポートされてへんみたいやわ"
 postToTheChannel: "チャンネルに投稿"
 cannotBeChangedLater: "後からは変えられへんで。"
-reactionAcceptance: "リアクションの受け入れ"
+reactionAcceptance: "ツッコミの受け入れ"
 likeOnly: "いいねだけ"
 likeOnlyForRemote: "リモートからはいいねだけな"
 rolesAssignedToMe: "自分に割り当てられたロール"
@@ -988,19 +1001,71 @@ retryAllQueuesConfirmText: "一時的にサーバー重なるかもしれへん
 enableChartsForRemoteUser: "リモートユーザーのチャートを作る"
 enableChartsForFederatedInstances: "リモートサーバーのチャートを作る"
 showClipButtonInNoteFooter: "ノートのアクションにクリップを追加"
-largeNoteReactions: "ノートのリアクションを大きする"
+largeNoteReactions: "ノートのツッコミを大きする"
 noteIdOrUrl: "ノートIDかURL"
+video: "å‹•ç”»"
+videos: "å‹•ç”»"
+dataSaver: "データケチケチ"
 accountMigration: "アカウントのお引っ越し"
 accountMoved: "このユーザーはさらのアカウントに引っ越したで:"
+accountMovedShort: "このアカウントは移行されとるで"
+operationForbidden: "この操作はできまへん"
 forceShowAds: "常に広告を表示しとく"
+addMemo: "メモを足す"
+editMemo: "メモをいらう"
+reactionsList: "ツッコミ一覧"
+renotesList: "Renote一覧"
+notificationDisplay: "通知見せる"
+leftTop: "左上"
+rightTop: "右上"
+leftBottom: "左下"
+rightBottom: "右下"
+stackAxis: "重ねる方向"
+vertical: "縦"
+horizontal: "横"
+position: "位置"
+serverRules: "サーバールール"
+pleaseConfirmBelowBeforeSignup: "このサーバーに登録する前に、下に書いてること確認してな。"
+pleaseAgreeAllToContinue: "続けるんやったら、全ての「せやな」にチェック入れてる必要があるで。"
+continue: "続けるで"
+preservedUsernames: "予約ユーザー名"
+preservedUsernamesDescription: "予約しとくユーザー名を行ごとに挙げるで。ここで指定されたユーザー名はアカウント作るときに使えへんくなるけど、管理者は例外や。あと、もうあるアカウントも例外やな。"
+createNoteFromTheFile: "このファイル使うてノート作るで"
+archive: "アーカイブ"
+channelArchiveConfirmTitle: "{name}をアーカイブしてええか?"
+channelArchiveConfirmDescription: "アーカイブしたら、チャンネル一覧とか検索結果からなくなるし、新しく書き込みもできへんなるで。"
+thisChannelArchived: "このチャンネル、アーカイブされとるで。"
+displayOfNote: "ノートの表示"
+initialAccountSetting: "初期設定"
+youFollowing: "フォロー中やで"
+_initialAccountSetting:
+  accountCreated: "アカウント作り終わったで。"
+  letsFillYourProfile: "最初はあんたのプロフィールを設定しよか。"
+  profileSetting: "プロフィール設定"
+  theseSettingsCanEditLater: "この設定はあとから変えれるで。"
+  youCanEditMoreSettingsInSettingsPageLater: "これ以外にもいろんな設定を「設定」ページからできるで。後で確認してみてな。"
+  followUsers: "タイムラインを構築するために、気になるユーザーをフォローしてみ。"
+  pushNotificationDescription: "プッシュ通知を有効にすると{name}の通知をあんたのデバイスで受け取れるで。"
+  initialAccountSettingCompleted: "初期設定が終わったで。"
+  haveFun: "{name}、楽しんでな~"
+  ifYouNeedLearnMore: "{name}(Misskey)の使い方とかをよー知りたいんやったら{link}をみてな。"
+  skipAreYouSure: "初期設定飛ばすか?"
+_serverRules:
+  description: "新規登録前に見せる、サーバーの簡潔なルールを設定すんで。内容は使うための決め事の要約とすることを推奨するわ。"
 _accountMigration:
-  moveTo: "このアカウントをさらのアカウントに引っ越すで"
-  moveToLabel: "引っ越し先のアカウント:"
-  moveAccountDescription: "この操作は戻されへんで。まず引っ越し先のアカウントでこのアカウントへのエイリアスが作れたか確認してきなはれや。エイリアスができてたら、引っ越し先のアカウントをこんな風に入力してくれへんか?:@person@instance.com"
   moveFrom: "別のアカウントからこのアカウントに引っ越す"
+  moveFromSub: "別のアカウントへエイリアスを作る"
   moveFromLabel: "引っ越し元のアカウント:"
   moveFromDescription: "別のアカウントからこのアカウントにフォロワーを引き継いで引っ越したかったら、ここでエイリアスを作っとく必要があるで。必ずお引っ越しを実行する前に作っとかなあかんで!引っ越し元のアカウントをこんな風に入力してくれへんか?:@person@instance.com"
+  moveTo: "このアカウントをさらのアカウントに引っ越すで"
+  moveToLabel: "引っ越し先のアカウント:"
+  moveCannotBeUndone: "アカウントを移行すると、取り消すことはできへんくなります。"
+  moveAccountDescription: "この操作は戻されへんで。まず引っ越し先のアカウントでこのアカウントへのエイリアスが作れたか確認してきなはれや。エイリアスができてたら、引っ越し先のアカウントをこんな風に入力してくれへんか?:@person@instance.com"
+  moveAccountHowTo: "アカウントの引っ越しには、まず引っ越し先のアカウントで自分のアカウントに対しエイリアスを作成しなはれや。\nエイリアス作成した後、引っ越し先のアカウントを次のように入力してくれへんか?:@username@server.example.com"
+  startMigration: "引っ越しする"
   migrationConfirm: "ほんまにこのアカウントを {account} に引っ越すんか?一回引っ越してもうたら取り消されへんし、二度とこのアカウントを元に戻されへんくなるで。\nそれと、引っ越し先のアカウントでエイリアスが作れたかちゃ~んと確認しーや?"
+  movedAndCannotBeUndone: "\nアカウントはもう引っ越されてます。\n引っ越しを取り消すことはできまへん。"
+  movedTo: "引っ越し先のアカウント:"
 _achievements:
   earnedAt: "貰った日ぃ"
   _types:
@@ -1172,6 +1237,9 @@ _achievements:
     _client30min:
       title: "ねんね"
       description: "クライアントを起動してから30分以上経過した"
+    _client60min:
+      title: "Misskeyの見過ぎや!"
+      description: "クライアント付けてから1時間経ってもうたで。"
     _noteDeletedWithin1min:
       title: "*おおっと*"
       description: "投稿してから1分以内にその投稿を消した"
@@ -1203,7 +1271,7 @@ _achievements:
       description: "ドライブのフォルダを再帰的な入れ子にしようとした"
     _reactWithoutRead:
       title: "ちゃんと読んだんか?"
-      description: "100文字以上のテキストを含むノートに投稿されてから3秒以内にリアクションした"
+      description: "100文字以上のテキストを含むノートに投稿されてから3秒以内にツッコんだ"
     _clickedClickHere:
       title: "ここをクリック"
       description: "ここをクリックした"
@@ -1260,6 +1328,8 @@ _role:
   iconUrl: "アイコン画像のURL"
   asBadge: "バッジとして見せる"
   descriptionOfAsBadge: "オンにすると、ユーザー名の横んとこにロールのアイコンが表示されるで。"
+  isExplorable: "ロールタイムラインを公開するで〜"
+  descriptionOfIsExplorable: "オンにしたらロールのタイムラインを公開するで〜。でもロールの公開をオフにしたら公開されへんよ。"
   displayOrder: "表示順"
   descriptionOfDisplayOrder: "数がでかいほど、UI上で先に表示されるで。"
   canEditMembersByModerator: "モデレーターのメンバー編集を許可"
@@ -1276,6 +1346,7 @@ _role:
     canInvite: "サーバー招待コードの発行"
     canManageCustomEmojis: "カスタム絵文字の管理"
     driveCapacity: "ドライブ容量"
+    alwaysMarkNsfw: "勝手にファイルにNSFWをくっつける"
     pinMax: "ノートのピン留めの最大数"
     antennaMax: "アンテナの作成可能数"
     wordMuteMax: "ワードミュートの最大文字数"
@@ -1527,32 +1598,16 @@ _time:
   minute: "分"
   hour: "時間"
   day: "æ—¥"
-_tutorial:
-  title: "Misskeyの使い方"
-  step1_1: "よう来たなあ"
-  step1_2: "この画面は「タイムライン」って言って、あんたや、あんたが「フォロー」する人の「ノート」が時系列で表示されるんやで。"
-  step1_3: "あんたはまだ何もノートを投稿してなくて、誰もフォローしてへんから、タイムラインには何も表示されてないはずやで。"
-  step2_1: "ノートを作ったり誰かをフォローしたりする前に、まずあんたのプロフィールを完成させよか。"
-  step2_2: "あんたがどんな人かわかると、多くの人にノートを見てもらえたり、フォローしてもらいやすくなるで。"
-  step3_1: "プロフィール設定はええ感じにできたか?"
-  step3_2: "ほな試しに、何かノートを投稿してみてやー。画面上にある鉛筆マークのボタンを押すとフォームが開くはずやで。"
-  step3_3: "内容を書いたら、フォーム右上のボタンを押すと投稿できるで。"
-  step3_4: "内容が思いつかへん?ほな「関西人なら面白いこと言うてえ〜や〜」とかどうやろか。"
-  step4_1: "投稿できたん?"
-  step4_2: "あんたのノートがタイムラインに表示されていれば成功やで"
-  step5_1: "次は、ほかの人をフォローしてタイムラインを賑やかにしよか"
-  step5_2: "{featured}で人気のノートが見れるから、その中から気になった人を選んでフォローしたり、{explore}で人気のユーザーを探すこともできるで。"
-  step5_3: "ユーザーをフォローしたかったら、ユーザーのアイコンをクリックしてユーザーページを表示して、「フォロー」ボタンを押すんやで。"
-  step5_4: "ユーザーによっては、フォローが承認されるまでちょっと時間がかかることがあるで。"
-  step6_1: "タイムラインに他のユーザーのノートが表示されていれば成功やで。"
-  step6_2: "他の人のノートには、「リアクション」を付けることができて、簡単にあんたの反応を伝えられるで。"
-  step6_3: "リアクションを付けるんやったら、ノートの「+」マークをクリックして、好きなリアクションを選択してな。"
-  step7_1: "これで、Misskeyの基本的な使い方の説明は終わりやで。お疲れさん。"
-  step7_2: "もっとMisskeyについて知りたいときは、{help}を見るとええかもな。"
-  step7_3: "ほな、Misskeyを楽しんでなー🚀"
-  step8_1: "最後に、プッシュ通知を有効化してみやん?"
-  step8_2: "プッシュ通知を受け取ることで、Misskeyを開いていない時でもリアクションやフォロー、メンションとかに気づけるで。"
-  step8_3: "通知の設定はあとから変更できるで"
+_timelineTutorial:
+  title: "Misskeyってなんや?"
+  step1_1: "これは「タイムライン」や。{name}に投稿された「ノート」が順番に表示されるで。"
+  step1_2: "タイムラインには何個か種類があってな、例えば「ホームタイムライン」だったらあんたのフォローしてる人のノート、「ローカルタイムライン」には{name}全部のノートが流れてくるで。"
+  step2_1: "試しに、何かノートを投稿してみ。画面の鉛筆マークのボタンでフォームが開くで。"
+  step2_2: "最初のノートは、自己紹介とか「{name}始めてみたんや」とかがええと思うで。"
+  step3_1: "投稿できた?"
+  step3_2: "あんたのノートがタイムラインに出てきたら成功や。"
+  step4_1: "ノートには、「リアクション」を付けれるで。"
+  step4_2: "ツッコむんやったら、ノートの「+」マークを押して、好きな絵文字を選ぶで。"
 _2fa:
   alreadyRegistered: "もう設定終わっとるわ。"
   registerTOTP: "認証アプリの設定はじめる"
@@ -1596,8 +1651,8 @@ _permissions:
   "write:notes": "ノートを作成・削除するで"
   "read:notifications": "通知を見るで"
   "write:notifications": "通知を操作するで"
-  "read:reactions": "リアクションを見る"
-  "write:reactions": "リアクションを操作するで"
+  "read:reactions": "ツッコミを見る"
+  "write:reactions": "ツッコミを操作するで"
   "write:votes": "投票する"
   "read:pages": "ページを見る"
   "write:pages": "ページを操作するで"
@@ -1853,7 +1908,7 @@ _notification:
     reply: "リプライ"
     renote: "Renote"
     quote: "引用"
-    reaction: "リアクション"
+    reaction: "ツッコミ"
     pollEnded: "アンケートが終了したで"
     receiveFollowRequest: "フォロー許可してほしいみたいやで"
     followRequestAccepted: "フォローが受理されたで"
@@ -1912,5 +1967,5 @@ _webhookSettings:
     note: "ノートを投稿したとき~!"
     reply: "返信があるとき~!"
     renote: "Renoteされるとき~!"
-    reaction: "リアクションがあるとき~!"
+    reaction: "ツッコミがあるとき~!"
     mention: "メンションがあるとき~!"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 3ff00482d609e77bb0486c10882a603f643fc24e..4a2fbe2a80d24b4fd85e938021c2f8e09f798d03 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -2,7 +2,7 @@
 _lang_: "한국어"
 headlineMisskey: "노트로 연결되는 네트워크"
 introMisskey: "환영합니다! Misskey는 오픈 소스 분산형 마이크로 블로그 서비스입니다.\n'노트'를 작성해서 지금 일어나고 있는 일을 공유하거나, 당신만의 이야기를 모두에게 발신하세요📡\n'리액션' 기능으로 친구의 노트에 총알같이 반응을 추가할 수도 있습니다👍\n새로운 세계를 탐험해 보세요🚀"
-poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼<b>Misskey</b>를 사용한 서버 중 하나입니다."
+poweredByMisskeyDescription: "{name}은(는) 오픈소스 플랫폼 <b>Misskey</b>를 사용한 서버 가운데 하나입니다."
 monthAndDay: "{month}월 {day}일"
 search: "검색"
 notifications: "알림"
@@ -20,6 +20,7 @@ noNotes: "노트가 없습니다"
 noNotifications: "표시할 알림이 없습니다"
 instance: "서버"
 settings: "설정"
+notificationSettings: "알림 설정"
 basicSettings: "기본 설정"
 otherSettings: "기타 설정"
 openInWindow: "창으로 열기"
@@ -262,14 +263,16 @@ noMoreHistory: "이것보다 과거의 기록이 없습니다"
 startMessaging: "대화 시작하기"
 nUsersRead: "{n}명이 읽음"
 agreeTo: "{0}에 동의"
+agree: "동의합니다"
 agreeBelow: "아래 내용에 동의합니다"
 basicNotesBeforeCreateAccount: "기본적인 주의사항"
-tos: "이용 약관"
+termsOfService: "이용 약관"
 start: "시작하기"
 home: "홈"
 remoteUserCaution: "리모트 유저이기 때문에, 정보가 정확하지 않을 수 있습니다."
 activity: "활동"
 images: "이미지"
+image: "이미지"
 birthday: "생일"
 yearsOld: "{age}세"
 registeredDate: "등록일"
@@ -473,6 +476,8 @@ createAccount: "계정 만들기"
 existingAccount: "기존 계정"
 regenerate: "재생성"
 fontSize: "글자 크기"
+mediaListWithOneImageAppearance: "이미지가 1개 뿐인 미디어 목록의 높이"
+limitTo: "{x}로 제한"
 noFollowRequests: "처리되지 않은 팔로우 요청이 없습니다"
 openImageInNewTab: "새 탭에서 이미지 열기"
 dashboard: "대시보드"
@@ -506,6 +511,7 @@ objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF
 objectStorageUseProxy: "연결에 프록시를 사용"
 objectStorageUseProxyDesc: "오브젝트 스토리지 API 호출시 프록시를 사용하지 않는 경우 OFF 로 설정해 주세요"
 objectStorageSetPublicRead: "업로드할 때 'public-read'를 설정하기"
+s3ForcePathStyleDesc: "s3ForcePathStyle을 활성화하면, 버킷 이름을 URL의 호스트명이 아닌 경로의 일부로써 취급합니다. 셀프 호스트 Minio와 같은 서비스를 사용할 경우 활성화해야 할 수 있습니다."
 serverLogs: "서버 로그"
 deleteAll: "모두 삭제"
 showFixedPostForm: "타임라인 상단에 글 작성란을 표시"
@@ -547,11 +553,14 @@ userSuspended: "이 계정은 정지된 상태입니다."
 userSilenced: "이 계정은 사일런스된 상태입니다."
 yourAccountSuspendedTitle: "계정이 정지되었습니다"
 yourAccountSuspendedDescription: "이 계정은 서버의 이용 약관을 위반하거나, 기타 다른 이유로 인해 정지되었습니다. 자세한 사항은 관리자에게 문의해 주십시오. 계정을 새로 생성하지 마십시오."
+tokenRevoked: "유효하지 않은 토큰입니다"
+tokenRevokedDescription: "로그인 토큰이 비활성화되었습니다. 다시 로그인하여 주십시오."
 accountDeleted: "계정이 정지되었습니다"
 accountDeletedDescription: "이 계정이 삭제되었습니다."
 menu: "메뉴"
 divider: "구분선"
 addItem: "항목 추가"
+rearrange: "ì •ë ¬"
 relays: "릴레이"
 addRelay: "릴레이 추가"
 inboxUrl: "Inbox 주소"
@@ -695,6 +704,8 @@ contact: "연락처"
 useSystemFont: "시스템 기본 글꼴을 사용"
 clips: "클립"
 experimentalFeatures: "실험실"
+experimental: "실험실"
+thisIsExperimentalFeature: "이 기능은 실험적인 기능입니다. 사양이 변경되거나 정상적으로 동작하지 않을 가능성이 있습니다."
 developer: "개발자"
 makeExplorable: "\"발견하기\"에 내 계정 보이기"
 makeExplorableDescription: "비활성화하면 \"발견하기\"에 나의 계정을 표시하지 않습니다."
@@ -901,6 +912,7 @@ remoteOnly: "리모트만"
 failedToUpload: "업로드 실패"
 cannotUploadBecauseInappropriate: "이 파일은 부적절한 내용을 포함한다고 판단되어 업로드할 수 없습니다."
 cannotUploadBecauseNoFreeSpace: "드라이브 용량이 부족하여 업로드할 수 없습니다."
+cannotUploadBecauseExceedsFileSizeLimit: "파일 크기가 너무 크기 때문에 업로드할 수 없습니다."
 beta: "베타"
 enableAutoSensitive: "자동 NSFW 탐지"
 enableAutoSensitiveDescription: "이용 가능할 경우 기계학습을 통해 자동으로 미디어 NSFW를 설정합니다. 이 기능을 해제하더라도, 서버 정책에 따라 자동으로 설정될 수 있습니다."
@@ -917,6 +929,7 @@ pushNotificationNotSupported: "브라우저나 서버에서 푸시 알림이 지
 sendPushNotificationReadMessage: "푸시 알림이나 메시지를 읽은 뒤 푸시 알림을 삭제"
 sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」이라는 알림이 잠깐 표시됩니다. 기기의 전력 소비량이 증가할 수 있습니다."
 windowMaximize: "최대화"
+windowMinimize: "최소화"
 windowRestore: "복구"
 caption: "캡션"
 loggedInAsBot: "봇 계정으로 로그인중"
@@ -933,6 +946,7 @@ didYouLikeMisskey: "Misskey가 마음에 드시나요?"
 pleaseDonate: "{host}은(는) 무료 소프트웨어 Misskey를 사용합니다. 후원을 통해 저희의 개발이 이어질 수 있게 도와주세요!"
 roles: "ì—­í• "
 role: "ì—­í• "
+noRole: "역할이 없습니다"
 normalUser: "일반 사용자"
 undefined: "정의되지 않음"
 assign: "할당"
@@ -942,6 +956,10 @@ manageCustomEmojis: "커스텀 이모지 관리"
 youCannotCreateAnymore: "더 이상 생성할 수 없습니다."
 cannotPerformTemporary: "일시적으로 사용할 수 없음"
 cannotPerformTemporaryDescription: "조작 횟수 제한을 초과하여 일시적으로 사용이 불가합니다. 잠시 후 다시 시도해 주세요."
+invalidParamError: "매개변수 오류"
+invalidParamErrorDescription: "요청 매개변수에 문제가 있습니다. 대부분의 경우 Misskey의 버그가 원인이지만, 입력 문자수가 너무 많았을 가능성 등도 있습니다."
+permissionDeniedError: "작업이 거부되었습니다"
+permissionDeniedErrorDescription: "이 작업을 수행할 권한이 없습니다."
 preset: "프리셋"
 selectFromPresets: "프리셋에서 선택"
 achievements: "도전 과제"
@@ -958,10 +976,96 @@ copyErrorInfo: "오류 정보 복사"
 joinThisServer: "이 서버에 가입"
 exploreOtherServers: "다른 서버 둘러보기"
 letsLookAtTimeline: "타임라인 구경하기"
+disableFederationConfirm: "정말로 연합을 끄시겠습니까?"
+disableFederationConfirmWarn: "연합을 끄더라도 게시물이 비공개로 전환되는 것은 아닙니다. 대부분의 경우 연합을 비활성화할 필요가 없습니다."
+disableFederationOk: "연합을 끄기"
 invitationRequiredToRegister: "현재 이 서버는 비공개입니다. 회원가입을 하시려면 초대 코드가 필요합니다."
 emailNotSupported: "이 서버에서는 메일 전송을 지원하지 않습니다"
 postToTheChannel: "채널에 게시하기"
 cannotBeChangedLater: "나중에 변경할 수 없습니다."
+reactionAcceptance: "리액션 수신"
+likeOnly: "좋아요만 받기"
+likeOnlyForRemote: "리모트에서는 좋아요만 받기"
+rolesAssignedToMe: "나에게 할당된 역할"
+resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
+sensitiveWords: "민감한 단어"
+sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
+notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
+license: "라이선스"
+unfavoriteConfirm: "즐겨찾기를 해제하시겠습니까?"
+myClips: "내 클립"
+drivecleaner: "드라이브 정리"
+retryAllQueuesNow: "모든 큐를 다시 시도"
+retryAllQueuesConfirmTitle: "지금 다시 시도하시겠습니까?"
+retryAllQueuesConfirmText: "일시적으로 서버의 부하가 증가할 수 있습니다."
+enableChartsForRemoteUser: "리모트 유저의 차트를 생성"
+enableChartsForFederatedInstances: "리모트 서버의 차트를 생성"
+showClipButtonInNoteFooter: "노트 동작에 클립을 추가"
+largeNoteReactions: "노트의 리액션을 크게 표시"
+noteIdOrUrl: "노트 ID 및 URL"
+video: "동영상"
+videos: "동영상"
+dataSaver: "데이터 절약 모드"
+accountMigration: "계정 이동"
+accountMoved: "이 사용자는 다음 계정으로 이사했습니다:"
+accountMovedShort: "이사한 계정입니다"
+operationForbidden: "사용할 수 없습니다"
+forceShowAds: "광고를 항상 표시"
+addMemo: "메모 추가"
+editMemo: "메모 편집"
+reactionsList: "리액션 목록"
+renotesList: "Renote 목록"
+notificationDisplay: "알림 표시"
+leftTop: "왼쪽 상단"
+rightTop: "오른쪽 상단"
+leftBottom: "왼쪽 하단"
+rightBottom: "오른쪽 하단"
+stackAxis: "나열 방향"
+vertical: "세로"
+horizontal: "가로"
+position: "위치"
+serverRules: "서버 규칙"
+pleaseConfirmBelowBeforeSignup: "이 서버에 가입하기 전에 아래 사항을 확인하여 주십시오."
+pleaseAgreeAllToContinue: "계속하시려면 모든 항목에 동의하십시오."
+continue: "계속"
+preservedUsernames: "예약된 사용자명"
+preservedUsernamesDescription: "예약할 사용자명을 한 줄에 하나씩 입력합니다. 여기에서 지정한 사용자명으로는 계정을 생성할 수 없게 됩니다. 단, 관리자 권한으로 계정을 생성할 때에는 해당되지 않으며, 이미 존재하는 계정도 영향을 받지 않습니다."
+createNoteFromTheFile: "이 파일로 노트를 작성"
+archive: "아카이브"
+channelArchiveConfirmTitle: "{name} 을(를) 아카이브하시겠습니까?"
+channelArchiveConfirmDescription: "아카이브한 채널은 채널 목록과 검색 결과에 표시되지 않으며, 채널에 새로운 노트를 작성할 수 없게 됩니다."
+thisChannelArchived: "이 채널은 아카이브되었습니다."
+displayOfNote: "노트 표시"
+initialAccountSetting: "초기 설정"
+youFollowing: "팔로잉"
+_initialAccountSetting:
+  accountCreated: "계정 생성이 완료되었습니다!"
+  letsFillYourProfile: "우선 나의 프로필을 설정해 보아요."
+  profileSetting: "프로필 설정"
+  theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있습니다."
+  youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 맛게 조절할 수 있습니다. 꼭 확인해 보세요!"
+  followUsers: "관심사가 맞는 유저를 팔로우하여 타임라인을 가꾸어 봅시다."
+  pushNotificationDescription: "푸시 알림을 활성화하면 {name}의 알림을 나의 기기에서 받아볼 수 있게 됩니다."
+  initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!"
+  haveFun: "{name}와 함께 즐거운 시간 보내세요!"
+  ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요."
+_serverRules:
+  description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
+_accountMigration:
+  moveFrom: "다른 계정에서 이 계정으로 이사"
+  moveFromSub: "다른 계정에 대한 별칭을 생성"
+  moveFromLabel: "기존 계정:"
+  moveFromDescription: "다른 계정에서 이 계정으로 팔로워를 가져오려면, 우선 여기에서 별칭을 지정해야 합니다. 반드시 이사하기 전에 지정해야 합니다! 기존 계정을 다음과 같은 형식으로 입력해 주십시오: @person@instance.com"
+  moveTo: "이 계정에서 다른 계정으로 이사"
+  moveToLabel: "이사할 계정:"
+  moveCannotBeUndone: "한 번 이사하면, 두 번 다시 되돌릴 수 없습니다."
+  moveAccountDescription: "이 작업은 취소할 수 없습니다. 먼저 이사할 계정에서 이 계정에 대한 별칭을 지정하였는지 다시 한 번 확인해 주십시오. 별칭을 지정한 다음, 이사할 계정을 다음과 같은 형식으로 입력해 주십시오: @person@instance.com"
+  moveAccountHowTo: "계정을 이사하려면 우선 이사갈 계정에서 이 계정에 대한 별칭을 지정해야 합니다.\n별칭을 작성한 다음, 이사갈 계정을 다음과 같이 입력하십시오:\n@username@server.example.com"
+  startMigration: "이사하기"
+  migrationConfirm: "정말로 이 계정을 {account} 으로 이전하시겠습니까? 한 번 이전한 다음에는 취소할 수 없으며, 두 번 다시 원래 상태로 복구할 수 없습니다.\n이사할 계정에서 계정 별칭을 지정하였는지 다시 한 번 확인하십시오."
+  movedAndCannotBeUndone: "\n이사한 계정입니다.\n이사는 취소할 수 없습니다."
+  postMigrationNote: "이 계정의 팔로잉 해제는 이사 후 24시간 뒤에 실행됩니다.\n이 계정의 팔로우 및 팔로워 수는 0으로 표시됩니다. 팔로워 해제는 이루어지지 않으므로, 당신의 팔로워는 이 계정의 팔로워 한정 게시물을 계속해서 열람할 수 있습니다."
+  movedTo: "이사할 계정:"
 _achievements:
   earnedAt: "달성 일시"
   _types:
@@ -1133,6 +1237,8 @@ _achievements:
     _client30min:
       title: "잠깐 쉬어"
       description: "클라이언트를 시작하고 30분이 경과하였습니다"
+    _client60min:
+      description: "클라이언트를 시작하고 60분이 경과하였습니다"
     _noteDeletedWithin1min:
       title: "있었는데요 없었습니다"
       description: "노트를 포스트한 후 1분 이내에 삭제했습니다"
@@ -1221,6 +1327,10 @@ _role:
   iconUrl: "아이콘 URL"
   asBadge: "뱃지로 표시"
   descriptionOfAsBadge: "활성화하면 유저명 옆에 역할의 아이콘이 표시됩니다."
+  isExplorable: "역할 타임라인 공개"
+  descriptionOfIsExplorable: "활성화하면 역할 타임라인을 공개합니다. 비활성화 시 타임라인이 공개되지 않습니다."
+  displayOrder: "표시 순서"
+  descriptionOfDisplayOrder: "값이 클 수록 UI에서 먼저 표시됩니다."
   canEditMembersByModerator: "모더레이터의 역할 수정 허용"
   descriptionOfCanEditMembersByModerator: "이 옵션을 켜면 모더레이터도 이 역할에 사용자를 할당하거나 삭제할 수 있습니다. 꺼져 있으면 관리자만 할당이 가능합니다."
   priority: "우선순위"
@@ -1235,6 +1345,7 @@ _role:
     canInvite: "서버 초대 코드 발행"
     canManageCustomEmojis: "커스텀 이모지 관리"
     driveCapacity: "드라이브 용량"
+    alwaysMarkNsfw: "파일을 항상 NSFW로 지정"
     pinMax: "고정할 수 있는 노트 수"
     antennaMax: "최대 안테나 생성 허용 수"
     wordMuteMax: "단어 뮤트할 수 있는 문자 수"
@@ -1246,15 +1357,18 @@ _role:
     rateLimitFactor: "요청 빈도 제한"
     descriptionOfRateLimitFactor: "작을수록 제한이 완화되고, 클수록 제한이 강화됩니다."
     canHideAds: "광고 숨기기"
+    canSearchNotes: "노트 검색 이용 가능 여부"
   _condition:
     isLocal: "로컬 사용자"
     isRemote: "리모트 사용자"
-    createdLessThan: "가압한 지 다음 일수 이내인 유저"
+    createdLessThan: "가입한 지 다음 일수 이내인 유저"
     createdMoreThan: "가입한 지 다음 일수 이상인 유저"
     followersLessThanOrEq: "팔로워 수가 다음 이하인 유저"
     followersMoreThanOrEq: "팔로워 수가 다음 이상인 유저"
     followingLessThanOrEq: "팔로잉 수가 다음 이하인 유저"
     followingMoreThanOrEq: "팔로잉 수가 다음 이상인 유저"
+    notesLessThanOrEq: "노트 수가 다음 이하인 유저"
+    notesMoreThanOrEq: "노트 수가 다음 이상인 유저"
     and: "다음을 모두 만족"
     or: "다음을 하나라도 만족"
     not: "다음을 만족하지 않음"
@@ -1278,8 +1392,8 @@ _ffVisibility:
   private: "비공개"
 _signup:
   almostThere: "거의 다 끝났습니다"
-  emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
-  emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
+  emailAddressInfo: "당신이 사용하고 있는 이메일 주소를 입력해 주세요. 이메일 주소는 다른 유저에게 공개되지 않습니다."
+  emailSent: "입력하신 메일 주소({email})로 확인 메일을 보내드렸습니다. 가입을 완료하시려면 보내드린 메일에 있는 링크로 접속해 주세요."
 _accountDelete:
   accountDelete: "계정 삭제"
   mayTakeTime: "계정 삭제는 서버에 부하를 가하기 때문에, 작성한 콘텐츠나 업로드한 파일의 수가 많으면 완료까지 시간이 걸릴 수 있습니다."
@@ -1364,6 +1478,8 @@ _channel:
   following: "팔로잉"
   usersCount: "{n}명 참여 중"
   notesCount: "{n}노트"
+  nameAndDescription: "이름과 설명"
+  nameOnly: "이름만"
 _menuDisplay:
   sideFull: "가로"
   sideIcon: "가로(아이콘)"
@@ -1481,32 +1597,6 @@ _time:
   minute: "분"
   hour: "시간"
   day: "일"
-_tutorial:
-  title: "Misskey의 사용 방법"
-  step1_1: "환영합니다!"
-  step1_2: "이 페이지는 \"타임라인\"이라고 불립니다. 당신이 \"팔로우\"하고 있는 사람들의 \"노트\"가 시간순으로 나타납니다."
-  step1_3: "아직 아무 유저도 팔로우하고 있지 않기에 타임라인은 비어 있을 것입니다."
-  step2_1: "새 노트를 작성하거나 다른 사람을 팔로우하기 전에, 먼저 프로필을 완성해보도록 합시다."
-  step2_2: "당신이 어떤 사람인지를 알린다면, 다른 사람들이 당신을 팔로우할 확률이 올라갈 것입니다."
-  step3_1: "프로필 설정은 잘 끝내셨나요?"
-  step3_2: "그럼 시험삼아 노트를 작성해 보세요. 화면에 있는 연필 버튼을 누르면 작성 폼이 열립니다."
-  step3_3: "내용을 작성한 후, 폼 오른쪽 상단의 버튼을 눌러 노트를 올릴 수 있습니다."
-  step3_4: "쓸 말이 없나요? \"Misskey 시작했어요!\" 같은 건 어떨까요? :>"
-  step4_1: "노트 작성을 끝내셨나요?"
-  step4_2: "당신의 노트가 타임라인에 표시되어 있다면 성공입니다."
-  step5_1: "이제, 다른 사람을 팔로우하여 타임라인을 활기차게 만들어보도록 합시다."
-  step5_2: "{featured}에서 이 서버의 인기 노트를 보실 수 있습니다. {explore}에서는 인기 사용자를 찾을 수 있구요. 마음에 드는 사람을 골라 팔로우해 보세요!"
-  step5_3: "다른 유저를 팔로우하려면 해당 유저의 아이콘을 클릭하여 프로필 페이지를 띄운 후, 팔로우 버튼을 눌러 주세요."
-  step5_4: "사용자에 따라 팔로우가 승인될 때까지 시간이 걸릴 수 있습니다."
-  step6_1: "타임라인에 다른 사용자의 노트가 나타난다면 성공입니다."
-  step6_2: "다른 유저의 노트에 \"리액션\"을 붙여 간단하게 당신의 반응을 전달할 수도 있습니다."
-  step6_3: "리액션을 붙이려면, 노트의 \"+\" 버튼을 클릭하고 원하는 이모지를 선택합니다."
-  step7_1: "이것으로 Misskey의 기본 튜토리얼을 마치겠습니다. 수고하셨습니다!"
-  step7_2: "Misskey에 대해 더 알고 싶으시다면 {help}를 참고해 주세요."
-  step7_3: "그럼 Misskey를 즐기세요! 🚀"
-  step8_1: "마지막으로, 푸시 알림을 활성화해 보지 않으실래요?"
-  step8_2: "푸시 알림을 활성화하면, Misskey를 열지 않았을 때에도 리액션이나 팔로우, 멘션 등을 확인할 수 있습니다."
-  step8_3: "알림 설정은 나중에도 변경할 수 있습니다."
 _2fa:
   alreadyRegistered: "이미 설정이 완료되었습니다."
   registerTOTP: "인증 앱 설정 시작"
@@ -1844,9 +1934,27 @@ _deck:
     channel: "채널"
     mentions: "받은 멘션"
     direct: "다이렉트"
+    roleTimeline: "역할 타임라인"
 _dialog:
   charactersExceeded: "최대 글자수를 초과하였습니다! 현재 {current} / 최대 {min}"
   charactersBelow: "최소 글자수 미만입니다! 현재 {current} / 최소 {min}"
+_disabledTimeline:
+  title: "비활성화된 타임라인"
+  description: "현재 역할에서는 이 타임라인을 이용할 수 없습니다."
+_drivecleaner:
+  orderBySizeDesc: "크기가 큰 순"
+  orderByCreatedAtAsc: "등록일이 오래된 순"
 _webhookSettings:
+  createWebhook: "Webhook 생성"
   name: "이름"
+  secret: "시크릿"
+  events: "Webhook을 실행할 타이밍"
   active: "활성화"
+  _events:
+    follow: "누군가를 팔로우했을 때"
+    followed: "누군가 나를 팔로우했을 때"
+    note: "노트를 게시할 때"
+    reply: "답글을 받았을 때"
+    renote: "누군가 내 글을 Renote했을 때"
+    reaction: "누군가 내 노트에 리액션했을 때"
+    mention: "누군가 나를 멘션했을 때"
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index 3f97b3982ca3b5ca63cc36db3bbf72bacd973e52..f686b04257debabe9228fea1251cf659976da2e8 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -211,11 +211,11 @@ uploadFromUrlRequested: "ຮ້ອງຂໍການອັບໂຫລດ"
 messageRead: "ອ່ານແລ້ວ"
 startMessaging: "ເລີ່ມການສົນທະນາໃໝ່"
 nUsersRead: "ອ່ານໂດຍ {n}"
-tos: "ເງື່ອນໄຂການໃຫ້ບໍລິການ"
 start: "ເລີ່ມຕົ້ນນຳໃຊ້ເລີຍ"
 home: "ໜ້າຫຼັກ"
 activity: "ກິດຈະກຳ"
 images: "ຮູບພາບ"
+image: "ຮູບພາບ"
 birthday: "ວັນເກີດ"
 yearsOld: "{age} ປີ"
 registeredDate: "ວັນທີ່ເປັນສະມາຊິກ"
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index 31c28a66e387aa3c7981733d2cd96ef7c4fc8a80..6bca484f141bd797ecbf67bee4216f2a49928e0f 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -254,12 +254,12 @@ noMoreHistory: "Er is geen verdere geschiedenis"
 startMessaging: "Start een gesprek"
 nUsersRead: "gelezen door {n}"
 agreeTo: "Ik stem in met {0}"
-tos: "Gebruiksvoorwaarden"
 start: "Aan de slag"
 home: "Startpagina"
 remoteUserCaution: "Aangezien deze gebruiker van een externe server afkomstig is, kan de weergegeven informatie onvolledig zijn."
 activity: "Activiteit"
 images: "Afbeeldingen"
+image: "Afbeeldingen"
 birthday: "Geboortedatum"
 yearsOld: "{age} jaar"
 registeredDate: "Inschrijvingsdatum"
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 83e189b9cf1c78ce3d50251015448b388294ddff..f7f0d442d474457f2efab76375472f9278909ecd 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -1,2 +1,483 @@
 ---
 _lang_: "Norsk Bokmål"
+search: "Søk"
+notifications: "Varsler"
+username: "Brukernavn"
+password: "Passord"
+forgotPassword: "Glemt passord"
+ok: "OK"
+gotIt: "Skjønner"
+cancel: "Avbryt"
+noThankYou: "Avbryt"
+noNotifications: "Ingen varsler"
+instance: "Server"
+settings: "Innstillinger"
+notificationSettings: "Varslingsinnstillinger"
+otherSettings: "Andre innstillinger"
+profile: "Profil"
+timeline: "Tidslinje"
+login: "Logg inn"
+loggingIn: "Logget inn"
+logout: "Logg ut"
+signup: "Bli med"
+uploading: "Laster opp"
+save: "Lagre"
+users: "Brukere"
+addUser: "Legg til bruker"
+favorite: "Favoritt"
+favorites: "Favoritter"
+unfavorite: "Fjern favoritt"
+pin: "Fest"
+unpin: "Opphev festing"
+copyContent: "Kopier innhold"
+copyLink: "Kopier lenke"
+delete: "Slett"
+deleteAndEdit: "Slett og rediger"
+addToList: "Legg til i liste"
+sendMessage: "Send melding"
+copyRSS: "Kopier RSS"
+copyUsername: "Kopier brukernavn"
+searchUser: "Søk brukere"
+reply: "Svar"
+loadMore: "Vis mer"
+showMore: "Vis mer"
+showLess: "Lukk"
+files: "Filer"
+download: "Nedlastinger"
+lists: "Lister"
+noLists: "Ingen lister"
+following: "Følg"
+followers: "Følgere"
+followsYou: "Følger deg"
+createList: "Opprett liste"
+error: "Feil"
+retry: "Prøv igjen"
+pageLoadError: "Kunne ikke hente side."
+privacy: "Personvern"
+follow: "Følg"
+followRequest: "Følgeforespørsel"
+followRequests: "Følgeforespørsel"
+unfollow: "Avfølg"
+followRequestPending: "Venter på godkjenning"
+quote: "Sitat"
+pinned: "Fest"
+you: "Du"
+clickToShow: "Klikk for å vise"
+add: "Legg til"
+reaction: "Reaksjon"
+reactions: "Reaksjoner"
+mute: "Skjul"
+unmute: "Vis"
+block: "Blokker"
+unblock: "Opphev blokkering"
+blockConfirm: "Blokker?"
+selectList: "Velg liste"
+selectChannel: "Velg kanal"
+emoji: "Emoji"
+emojis: "Emojier"
+addEmoji: "Legg til emoji"
+addAccount: "Legg til konto"
+selectUser: "Velg bruker"
+instances: "Server"
+registeredAt: "Registrerte seg"
+perHour: "Per time"
+perDay: "Per dag"
+blockThisInstance: "Blokker denne serveren"
+version: "Versjon"
+statistics: "Statistikk"
+clearQueue: "Tøm kø"
+clearQueueConfirmTitle: "Vil du tømme kø?"
+blockedInstances: "Blokkerte severe"
+muteAndBlock: "Skjul og blokker"
+mutedUsers: "Skjulte brukere"
+blockedUsers: "Blokkerte brukere"
+editProfile: "Rediger profil"
+pinLimitExceeded: "Du kan ikke feste flere."
+noCustomEmojis: "Ingen emoji"
+blocked: "Blokkert"
+all: "Alle"
+changePassword: "Endre passord"
+security: "Sikkerhet"
+newPassword: "Nytt passord"
+newPasswordRetype: "Nytt passord (gjenta)"
+more: "Mer!"
+remove: "Slett"
+removed: "Slettet"
+saved: "Lagret"
+upload: "Laste opp"
+explore: "Utforsk"
+messageRead: "Lest"
+agree: "Jeg godtar"
+home: "Hjem"
+images: "Bilder"
+image: "Bilder"
+birthday: "Bursdag"
+yearsOld: "{age} år gammel"
+light: "Lys"
+dark: "Mørk"
+fileName: "Filnavn"
+selectFile: "Velg fil"
+selectFiles: "Velg fil"
+selectFolder: "Velg mappe"
+selectFolders: "Velg mappe"
+renameFile: "Endre filnavn"
+folderName: "Mappenavn"
+createFolder: "Opprett mappe"
+renameFolder: "Endre mappenavn"
+deleteFolder: "Slett mappe"
+addFile: "Legg til fil"
+emptyFolder: "Denne mappen er tom"
+copyUrl: "Kopier URL"
+rename: "Endre navn"
+doNothing: "Gjør ingenting"
+accept: "Tillatt"
+reject: "Avslå"
+instanceName: "Servernavn"
+thisYear: "I år"
+today: "I dag"
+pages: "Sider"
+pinnedUsers: "Festete brukrere"
+pinnedPages: "Festete sider"
+hcaptcha: "hCaptcha"
+recaptcha: "reCAPTCHA"
+name: "Navn"
+popularUsers: "Populære brukere"
+exploreUsersCount: "Det finnes {count} brukere"
+userList: "Lister"
+about: "Infomasjon"
+aboutMisskey: "Om Misskey"
+share: "Del"
+help: "Hjelp"
+close: "Lukk"
+members: "Medlemmer"
+text: "Tekst"
+next: "Neste"
+retype: "Gjenta"
+available: "Tilgjengelig"
+unavailable: "Utilgjengelig"
+tooShort: "For kort"
+tooLong: "For langt"
+or: "eller"
+language: "Språk"
+aboutX: "Om {x}"
+category: "Kategorier"
+createAccount: "Opprett konto"
+objectStorageRegion: "Region"
+objectStorageUseSSL: "Bruk SSL"
+objectStorageUseProxy: "Bruk Proxy"
+deleteAll: "Slett alt"
+listen: "Lytt"
+none: "Ingen"
+chooseEmoji: "Velg emoji"
+recentUsed: "Sist brukte"
+install: "Installer"
+nothing: "Ingenting"
+deleteAllFiles: "Slett alle filer"
+deleteAllFilesConfirm: "Vil du slette alle filer?"
+accountDeleted: "Kontoen blir slettet"
+accountDeletedDescription: "Denne kontoen blir slettet"
+menu: "Meny"
+poll: "Avstemning"
+description: "Beskrivelse"
+author: "Forfatter"
+height: "Høyde"
+large: "Stor"
+small: "Liten"
+notificationType: "Varseltype"
+edit: "Rediger"
+email: "E-post"
+smtpUser: "Brukernavn"
+smtpPass: "Passord"
+userSaysSomething: "{name} sa noe"
+copy: "Kopier"
+channel: "Kanaler"
+create: "Opprett"
+notificationSetting: "Varslingsinnstillinger"
+other: "Andre"
+behavior: "Oppførsel"
+sample: "Eksempel"
+abuseReports: "Rappoter"
+reportAbuse: "Rappoter"
+send: "Send"
+openInNewTab: "Ã…pne i ny fane"
+waitingFor: "Venter på {x}"
+system: "System"
+followingCount: "Følger"
+followersCount: "Følgere"
+yes: "Ja"
+no: "Nei"
+left: "Venstre"
+saveAs: "Lagre som"
+value: "Verdi"
+deleteConfirm: "Vil du slette?"
+invalidValue: "Verdien er ugyldig."
+emailNotification: "E-postvarsler"
+inChannelSearch: "Søk i kanal"
+clear: "Tøm"
+markAllAsRead: "Merk alt som lest"
+addDescription: "Legg til beskrivelse"
+info: "Infomasjon"
+unknown: "Ukjent"
+selectAccount: "Velg konto"
+user: "Brukere"
+accounts: "Kontoer"
+switch: "Bytt"
+gallery: "Galleri"
+ads: "Annonser"
+high: "Høy"
+low: "Lav"
+sent: "Send"
+learnMore: "Les mer"
+translate: "Oversett"
+unread: "Ulest"
+manageAccounts: "Administrer konto"
+classic: "Klassisk"
+muteThread: "Skjul denne tråden"
+unmuteThread: "Vis denne tråden"
+tablet: "Nettbrett"
+auto: "Automatisk"
+size: "Størrelse"
+searchByGoogle: "Søk"
+tenMinutes: "10 minutter"
+oneHour: "1 time"
+oneDay: "1 dag"
+oneWeek: "1 uke"
+oneMonth: "1 måned"
+file: "Filer"
+recommended: "Anbefalt"
+check: "Sjekk"
+deleteAccount: "Slett konto"
+document: "Dokumenter"
+logoutConfirm: "Vil du logge ut?"
+pleaseSelect: "Vennligst velg"
+type: "Type"
+beta: "Beta"
+account: "Kontoer"
+move: "Flytt"
+pushNotification: "Push-varsler"
+tools: "Verktøy"
+like: "Liker!"
+unlike: "Liker ikke"
+numberOfLikes: "Likerklikk"
+show: "Vis"
+neverShow: "Ikke vis igjen"
+remindMeLater: "Kanskje senere"
+didYouLikeMisskey: "Likte du Misskey?"
+roles: "Roller"
+role: "Rolle"
+color: "Farge"
+youCannotCreateAnymore: "Du kan ikke opprette flere."
+cannotPerformTemporary: "Midlertidig utilgjengelig"
+thisPostMayBeAnnoyingCancel: "Avbryt"
+exploreOtherServers: "Utforsk andre severe"
+letsLookAtTimeline: "La oss se på tidslinje"
+cannotBeChangedLater: "Du kan ikke endre senere."
+likeOnly: "Bare liker"
+retryAllQueuesConfirmTitle: "Vil du prøve igjen akkurat nå?"
+video: "Video"
+videos: "Videoer"
+continue: "Fortsett"
+youFollowing: "Følger"
+_initialAccountSetting:
+  theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
+_achievements:
+  _types:
+    _notes100000:
+      flavor: "Du har jammen mye å si."
+    _noteFavorited1:
+      title: "Stjernekikker"
+    _myNoteFavorited1:
+      title: "Jeg vil gjerne få en stjerne"
+    _following50:
+      title: "Mange venner"
+    _following100:
+      title: "100 venner"
+    _following300:
+      title: "For mange venner"
+    _followers10:
+      title: "Følg meg!"
+    _followers100:
+      title: "Populær"
+    _postedAtLateNight:
+      flavor: "Det er på tide å gå til sengs."
+    _driveFolderCircularReference:
+      title: "Rundskrivreferanse"
+    _reactWithoutRead:
+      title: "Leste du det virkelig?"
+    _clickedClickHere:
+      title: "Klikk her"
+      description: "Du har klikket her"
+    _justPlainLucky:
+      title: "Rett og slett heldig"
+    _setNameToSyuilo:
+      description: "Du har satt navnet ditt til \"syuilo\""
+    _loggedInOnBirthday:
+      title: "Gratulerer med dagen"
+    _loggedInOnNewYearsDay:
+      title: "Godt nytt år"
+    _brainDiver:
+      title: "Brain Diver"
+      flavor: "Misskey-Misskey La-Tu-Ma"
+_role:
+  options: "Alternativ"
+  _priority:
+    low: "Lav"
+    high: "Høy"
+_emailUnavailable:
+  used: "Allerede brukt"
+_accountDelete:
+  accountDelete: "Slett konto"
+_ad:
+  hide: "Ikke vis"
+_gallery:
+  like: "Liker!"
+  unlike: "Liker ikke"
+_preferencesBackups:
+  saveNew: "Lagre som ny"
+  cannotSave: "Kunne ikke lagre"
+_registry:
+  key: "Nøkkel"
+  keys: "Nøkler"
+_aboutMisskey:
+  about: "Misskey er programvare med åpen kildekode som har blitt utviklet av syuilo siden 2014."
+  translation: "Oversett Misskey"
+_instanceTicker:
+  none: "Ikke vis"
+  always: "Alltid vis"
+_channel:
+  create: "Opprett kanal"
+  edit: "Rediger kanal"
+  featured: "Populært"
+  following: "Følger"
+  nameAndDescription: "Navn og beskrivelse"
+_wordMute:
+  soft: "Myk"
+  hard: "Hard"
+_theme:
+  description: "Beskrivelse"
+  color: "Farge"
+  key: "Nøkkel"
+  keys:
+    link: "Lenke"
+_sfx:
+  notification: "Varsler"
+_ago:
+  future: "Fremitid"
+  justNow: "Akkurat nå"
+  secondsAgo: "{n} sekunder siden"
+  minutesAgo: "{n} minutter siden"
+  hoursAgo: "{n} timer siden"
+  daysAgo: "{n} dager siden"
+  weeksAgo: "{n} uker siden"
+  monthsAgo: "{n} måneder siden"
+  yearsAgo: "{n} år siden"
+  invalid: "Ingenting"
+_time:
+  second: "Sekunder"
+  minute: "Minutter"
+  hour: "Timer"
+  day: "Dager"
+_timelineTutorial:
+  title: "Hvordan bruke Misskey"
+_2fa:
+  renewTOTPCancel: "Avbryt"
+_weekday:
+  sunday: "Søndag"
+  monday: "Mandag"
+  tuesday: "Tirsdag"
+  wednesday: "Onsdag"
+  thursday: "Torsdag"
+  friday: "Fredag"
+  saturday: "Lørdag"
+_widgets:
+  profile: "Profil"
+  notifications: "Varsler"
+  timeline: "Tidslinje"
+  calendar: "Kalender"
+  trends: "Populært"
+  clock: "Klokke"
+  photos: "Bilder"
+  button: "Knapp"
+  aiscriptApp: "AiScript App"
+  userList: "Brukerliste"
+  _userList:
+    chooseList: "Velg liste"
+_cw:
+  show: "Vis mer"
+_poll:
+  noOnlyOneChoice: "Trenger minst to valger."
+  choiceN: "Valg {n}"
+  noMore: "Du kan ikke legge til flere."
+  deadlineTime: "Timer"
+  votesCount: "{n} stemmer"
+  vote: "Stem"
+  showResult: "Vis resultatet"
+  voted: "Stemt"
+  closed: "Avsluttet"
+_visibility:
+  home: "Hjem"
+  followers: "Følgere"
+_postForm:
+  _placeholders:
+    a: "Hva skjer?"
+_profile:
+  name: "Navn"
+  username: "Brukernavn"
+  metadataContent: "Innhold"
+_exportOrImport:
+  followingList: "Følg"
+  muteList: "Skjul"
+  blockingList: "Blokker"
+  userLists: "Lister"
+_charts:
+  filesIncDec: "Forskjell på antall filer"
+_instanceCharts:
+  users: "Forskjell på antall brukere"
+  ff: "Forskjell på antall Følg/Følgere"
+  files: "Forskjell på antall filer"
+_timelines:
+  home: "Hjem"
+_play:
+  new: "Opprett Play"
+  edit: "Rediger Play"
+  featured: "Populært"
+  summary: "Beskrivelse"
+_pages:
+  like: "Liker"
+  unlike: "Liker ikke"
+  my: "Mine sider"
+  featured: "Populært"
+  contents: "Innhold"
+  url: "Side URL"
+  fontSerif: "Serif"
+  fontSansSerif: "Sans Serif"
+  selectType: "Velg type"
+  blocks:
+    text: "Tekst"
+    section: "Seksjon"
+    image: "Bilde"
+    button: "Knapp"
+_notification:
+  _types:
+    follow: "Følg"
+    reply: "Svar"
+    quote: "Sitat"
+    reaction: "Reaksjon"
+  _actions:
+    reply: "Svar"
+_deck:
+  swapLeft: "Flytt til venstre"
+  swapRight: "Flytt til høyre"
+  swapUp: "Flytt opp"
+  swapDown: "Flytt ned"
+  profile: "Profil"
+  newProfile: "Ny profil"
+  deleteProfile: "Slett profil"
+  _columns:
+    notifications: "Varsler"
+    tl: "Tidslinje"
+    list: "Lister"
+    channel: "Kanaler"
+    direct: "Direkte"
+_webhookSettings:
+  name: "Navn"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 517e8431f9582f1d24839ae71ed81f4f7f197995..24eee60bae43db8b0be985071a66b2dcd6773ed9 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -257,12 +257,12 @@ startMessaging: "Rozpocznij czat"
 nUsersRead: "przeczytano przez {n}"
 agreeTo: "Wyrażam zgodę na {0}"
 agreeBelow: "Zaakceptuj poniżej"
-tos: "Regulamin"
 start: "Rozpocznij"
 home: "Strona główna"
 remoteUserCaution: "Te informacje mogą nie być aktualne, ponieważ użytkownik pochodzi ze zdalnej instancji."
 activity: "Aktywność"
 images: "Zdjęcia"
+image: "Zdjęcia"
 birthday: "Data urodzenia"
 yearsOld: "{age} lat"
 registeredDate: "Zarejestrowano"
@@ -870,6 +870,7 @@ loggedInAsBot: "JesteÅ› obecnie zalogowany/a jako bot"
 like: "Polub"
 show: "Wyświetlanie"
 color: "Kolor"
+youFollowing: "Åšledzeni"
 _role:
   priority: "Priorytet"
   _priority:
@@ -1086,32 +1087,6 @@ _time:
   minute: "minuta"
   hour: "godz."
   day: "dzień"
-_tutorial:
-  title: "Jak korzystać z Misskey"
-  step1_1: "Witaj!"
-  step1_2: "Ta strona nazywa się „oś czasu”. Pokazuje chronologicznie uporządkowane wpisy osób, które „śledzisz”."
-  step1_3: "Twoja oś czasu jest jeszcze pusta, ponieważ nie opublikowałeś(-aś) jeszcze żadnych wpisów i nie obserwujesz jeszcze nikogo."
-  step2_1: "Ukończmy konfigurację profilu zanim utworzymy wpis lub zaczniemy kogoś obserwować."
-  step2_2: "Podanie pewnych informacji o tym, kim jesteś, ułatwi innym określenie, czy chcą widzieć Twoje wpisy lub Cię obserwować."
-  step3_1: "Zakończyłeś(-aś) konfigurację profilu?"
-  step3_2: "Następnie spróbujmy opublikować wpis. Możesz to zrobić, naciskając przycisk z ikoną ołówka na ekranie."
-  step3_3: "Wypełnij pole i kliknij przycisk w prawym górnym rogu by wysłać post."
-  step3_4: "Nie masz nic do powiedzenia? Spróbuj \"ustawiam swój misskey\"!"
-  step4_1: "Zakończyłeś publikowanie pierwszego wpisu?"
-  step4_2: "Hurra! Teraz Twój pierwszy wpis powinien być wyświetlany na Twojej osi czasu."
-  step5_1: "Teraz spróbujmy ożywić Twoją oś czasu, przez zaobserwowanie innych ludzi."
-  step5_2: "{featured} pokaże Ci popularne wpisy na tej instancji. {explore} pozwoli Ci znaleźć popularnych użytkowników. Spróbuj znaleźć tam osoby, które chcesz obserwować!"
-  step5_3: "Aby obserwować innych użytkowników, kliknij ich ikonę i naciśnij przycisk \"Obserwuj\" na ich profilu."
-  step5_4: "Jeśli inny użytkownik ma ikonę kłódki obok swojej nazwy, może minąć trochę czasu, zanim ten użytkownik ręcznie zatwierdzi Twoją prośbę o obserwowanie."
-  step6_1: "Powinieneś teraz widzieć wpisy innych użytkowników na swojej osi czasu."
-  step6_2: "Możesz także umieścić „reakcje” na wpisach innych osób, aby szybko na nie odpowiedzieć."
-  step6_3: "Aby dodać \"reakcję\", naciśnij znak \"+\" na wpisie innego użytkownika i wybierz emotikonę, którą chcesz zareagować."
-  step7_1: "Gratulacje! Ukończyłeś podstawowy samouczek Misskey."
-  step7_2: "Jeśli chcesz dowiedzieć się więcej o Misskey, wypróbuj sekcję {help}."
-  step7_3: "A teraz powodzenia i baw się dobrze z Misskey! 🚀"
-  step8_1: "Na sam koniec, czy nie chciał(a)byś włączyć powiadomień push?"
-  step8_2: "Włączenie tej opcji pozwoli ci otrzymywać powiadomienia o reakcjach, śledzeniach i wzmiankach nawet wtedy, gdy Misskey nie będzie otwarty."
-  step8_3: "Ustawienia powiadomień można zmienić później."
 _2fa:
   alreadyRegistered: "Zarejestrowałeś już urządzenie do uwierzytelniania dwuskładnikowego."
   step1: "Najpierw, zainstaluj aplikacjÄ™ uwierzytelniajÄ…cÄ… (takÄ… jak {a} lub {b}) na swoim urzÄ…dzeniu."
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 8a7232fa2c607a67fceabaefca8b6537d937e02d..e7b43f63803bc94b5aa76c91e069212917604c6a 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -252,12 +252,12 @@ noMoreHistory: "Sem mais história"
 startMessaging: "Iniciar conversação"
 nUsersRead: "{n} Pessoas leem"
 agreeTo: "Eu concordo com {0}"
-tos: "Termos de serviço"
 start: "começar"
 home: "casa"
 remoteUserCaution: "As informações estão incompletas porque é um utilizador remoto."
 activity: "atividade"
 images: "imagem"
+image: "imagem"
 birthday: "aniversário"
 yearsOld: "{age} anos"
 registeredDate: "Data de registro"
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index bbf0160de96e35a797289946ff5053d57c3a140f..6ad45d06366c503638688b2ad64513bcc6ca8dcc 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -252,12 +252,12 @@ noMoreHistory: "Nu există mai mult istoric"
 startMessaging: "ÃŽncepe un chat nou"
 nUsersRead: "citit de {n}"
 agreeTo: "Sunt de acord cu {0}"
-tos: "Termenii de utilizare"
 start: "Să începem"
 home: "Acasă"
 remoteUserCaution: "Deoarece acest utilizator este dintr-o instanță externă, informația afișată poate fi incompletă."
 activity: "Activitate"
 images: "Imagini"
+image: "Imagini"
 birthday: "Zi de naștere"
 yearsOld: "{age} ani"
 registeredDate: "Data înregistrării"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 8a09d303067ce4ce3c6f10123f0ec72f76d4a0a0..55d4b70590be8e22ca8f45173e3b819fdf0f9098 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -20,6 +20,7 @@ noNotes: "Нет ни одной заметки"
 noNotifications: "Нет ни одного уведомления"
 instance: "Инстанс"
 settings: "Настройки"
+notificationSettings: "Настройки уведомлений"
 basicSettings: "Основные настройки"
 otherSettings: "Прочие настройки"
 openInWindow: "Открыть в плавающем окне"
@@ -122,6 +123,8 @@ unmarkAsSensitive: "Снять отметку «не для всех»"
 enterFileName: "Введите имя файла"
 mute: "Скрыть"
 unmute: "Отменить скрытие"
+renoteMute: "Заглушить репосты"
+renoteUnmute: "Включить репосты"
 block: "Заблокировать"
 unblock: "Разблокировать"
 suspend: "Заморозить"
@@ -153,6 +156,7 @@ flagShowTimelineReplies: "Показывать ответы на заметки
 flagShowTimelineRepliesDescription: "Если этот параметр включен, то в ленте, в дополнение к заметкам пользователя, отображаются ответы на другие заметки пользователя."
 autoAcceptFollowed: "Принимать подписчиков автоматически"
 addAccount: "Добавить учётную запись"
+reloadAccountsList: "Обновить список учётных записей"
 loginFailed: "Неудачная попытка входа"
 showOnRemote: "Перейти к оригиналу на сайт"
 general: "Общее"
@@ -259,14 +263,16 @@ noMoreHistory: "История закончилась"
 startMessaging: "Начать общение"
 nUsersRead: "Прочитали {n}"
 agreeTo: "Я соглашаюсь с {0}"
+agree: "Согласиться"
 agreeBelow: "Согласен со следующими"
 basicNotesBeforeCreateAccount: "Записи, перед созданием аккаунта"
-tos: "Пользовательское соглашение"
+termsOfService: "Условия использования"
 start: "Начать"
 home: "Главная"
 remoteUserCaution: "Это пользователь с другого сайта, поэтому информация может быть неточной."
 activity: "Активность"
 images: "Изображения"
+image: "Изображения"
 birthday: "День рождения"
 yearsOld: "Возраст: {age}"
 registeredDate: "Дата регистрации"
@@ -457,6 +463,7 @@ aboutX: "Описание {x}"
 emojiStyle: "Стиль эмодзи"
 native: "Системные"
 disableDrawer: "Не использовать выдвижные меню"
+showNoteActionsOnlyHover: "Показывать кнопки управления заметкой только при наведении"
 noHistory: "История пока пуста"
 signinHistory: "Журнал посещений"
 enableAdvancedMfm: "Включить расширенный MFM"
@@ -469,6 +476,8 @@ createAccount: "Новая учётная запись"
 existingAccount: "Существующая учётная запись"
 regenerate: "Создать повторно"
 fontSize: "Размер шрифта"
+mediaListWithOneImageAppearance: "Показывать список медиа только одним изображением"
+limitTo: "Обрезать до {x}"
 noFollowRequests: "Нерассмотренные запросы на подписку отсутствуют"
 openImageInNewTab: "Открыть изображение в новой вкладке"
 dashboard: "Панель управления"
@@ -502,9 +511,11 @@ objectStorageUseSSLDesc: "Отключите, если не собираетес
 objectStorageUseProxy: "Использовать прокси"
 objectStorageUseProxyDesc: "Отключите, если не будете испоьзовать прокси для соединений по протоколу ObjectStorage."
 objectStorageSetPublicRead: "Устанавливать public-read при загрузке на сервер"
+s3ForcePathStyleDesc: "Включение s3ForcePathStyle принудительно указывает имя корзины как часть пути в URL-адресе вместо имени хоста. Может потребоваться активация при использовании таких вещей, как локальный Minio."
 serverLogs: "Журнал сервера"
 deleteAll: "Удалить всё"
 showFixedPostForm: "Показывать поле для ввода новой заметки наверху ленты"
+showFixedPostFormInChannel: "Показывать поле для ввода новой заметки наверху ленты (каналы)"
 newNoteRecived: "Появилась новая заметка"
 sounds: "Звуки"
 sound: "Звуки"
@@ -542,6 +553,10 @@ userSuspended: "Эта учётная запись заморожена"
 userSilenced: "Этот пользователь был заглушен"
 yourAccountSuspendedTitle: "Эта учетная запись заблокирована"
 yourAccountSuspendedDescription: "Эта учетная запись была заблокирована из-за нарушения условий предоставления услуг сервера. Свяжитесь с администратором, если вы хотите узнать более подробную причину. Пожалуйста, не создавайте новую учетную запись."
+tokenRevoked: "Токен недействителен"
+tokenRevokedDescription: "Срок действия вашего токена входа истек. Пожалуйста, войдите снова."
+accountDeleted: "Эта учетная запись удалена"
+accountDeletedDescription: "Эта учетная запись удалена"
 menu: "Меню"
 divider: "Линия-разделитель"
 addItem: "Добавить элемент"
@@ -688,6 +703,8 @@ contact: "Как связаться"
 useSystemFont: "Использовать шрифт, предлагаемый системой"
 clips: "Подборки"
 experimentalFeatures: "Экспериментальные функции"
+experimental: "Экспериментальные"
+thisIsExperimentalFeature: "Это экспериментальная функция. Технические характеристики могут измениться или он может работать неправильно."
 developer: "Разработчик"
 makeExplorable: "Опубликовать профиль в «Обзоре»."
 makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»."
@@ -852,6 +869,7 @@ tenMinutes: "10 минут"
 oneHour: "1 час"
 oneDay: "1 день"
 oneWeek: "1 неделя"
+oneMonth: "1 месяц"
 reflectMayTakeTime: "Изменения могут занять время для отображения"
 failedToFetchAccountInformation: "Не удалось получить информацию об аккаунте"
 rateLimitExceeded: "Ограничение скорости превышено"
@@ -893,6 +911,7 @@ remoteOnly: "Только удалённо"
 failedToUpload: "Сбой выгрузки"
 cannotUploadBecauseInappropriate: "Файл не может быть загружен, так как было установлено, что он может содержать неприемлемое содержимое."
 cannotUploadBecauseNoFreeSpace: "Файл не может быть загружен, так как не осталось места на диске"
+cannotUploadBecauseExceedsFileSizeLimit: "Файл не может быть загружен, так как он превышает лимит размера файла."
 beta: "Бета"
 enableAutoSensitive: "Автоматическое определение NSFW"
 enableAutoSensitiveDescription: "Если доступно, используйте машинное обучение для автоматической установки флага NSFW на носителе. Даже если эта функция отключена, она может быть установлена ​​автоматически в зависимости от инстанта."
@@ -909,6 +928,7 @@ pushNotificationNotSupported: "Push-уведмления не поддержив
 sendPushNotificationReadMessage: "Удалять push-уведомления когда сообщение или прочитано"
 sendPushNotificationReadMessageCaption: "На мгновение появится уведомление \"{emptyPushNotificationMessage}\". Расход заряда батареи может увеличиться "
 windowMaximize: "Развернуть"
+windowMinimize: "Свернуть"
 windowRestore: "Восстановить"
 caption: "Подпись (Automatic Translation)"
 loggedInAsBot: "Вы под аккаунтом бота!"
@@ -925,6 +945,7 @@ didYouLikeMisskey: "Вам нравится Misskey?"
 pleaseDonate: "Сайт {host} работает на Misskey. Это бесплатное программное обеспечение, и ваши пожертвования очень бы помогли продолжать его разработку!"
 roles: "Роли"
 role: "Роль"
+noRole: "Нет роли"
 normalUser: "Обычный пользователь"
 undefined: "неопределён"
 assign: "Назначить"
@@ -934,6 +955,10 @@ manageCustomEmojis: "Управлять пользовательскими эм
 youCannotCreateAnymore: "Вы достигли лимита создания."
 cannotPerformTemporary: "Временно недоступен"
 cannotPerformTemporaryDescription: "Это действие временно невозможно выполнить из-за превышения лимита выполнения."
+invalidParamError: "Ошибка параметра"
+invalidParamErrorDescription: "Проблема с параметрами запроса. Обычно это ошибка, но это также может быть связано с тем, что вы набрали слишком много символов."
+permissionDeniedError: "Операция запрещена"
+permissionDeniedErrorDescription: "У этой учетной записи нет разрешения на выполнение этой операции."
 preset: "Шаблоны"
 selectFromPresets: "Выбрать из шаблонов"
 achievements: "Достижения"
@@ -950,6 +975,37 @@ copyErrorInfo: "Скопировать код ошибки"
 joinThisServer: "Присоединяйтесь к этому серверу"
 exploreOtherServers: "Искать другие сервера"
 letsLookAtTimeline: "Давайте посмотрим на ленту"
+disableFederationConfirm: "Отключить федерацию?"
+disableFederationConfirmWarn: "Дефедерация не делает заметку приватной. В большинстве случаев без федерации не обойтись."
+disableFederationOk: "Не федерируется"
+invitationRequiredToRegister: "Этот сервер в настоящее время только по приглашению. Зарегистрироваться могут только те, у кого есть код приглашения."
+emailNotSupported: "Доставка почты не поддерживается на этом сервере"
+postToTheChannel: "Отправить в канал"
+cannotBeChangedLater: "Это нельзя изменить позже"
+reactionAcceptance: "Принятие реакций"
+likeOnly: "Только лайки"
+likeOnlyForRemote: "Только лайки с удалённых серверов"
+rolesAssignedToMe: "Мои роли"
+resetPasswordConfirm: "Сбросить пароль?"
+sensitiveWords: "Чувствительные слова"
+sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
+notesSearchNotAvailable: "Поиск заметок недоступен"
+license: "Лицензия"
+unfavoriteConfirm: "Удалить избранное?"
+myClips: "Мои клипы"
+drivecleaner: "Очиститель дисков"
+retryAllQueuesNow: "Повторить все очереди сейчас"
+retryAllQueuesConfirmTitle: "Хотите попробовать ещё раз?"
+retryAllQueuesConfirmText: "Нагрузка на сервер может увеличиться"
+enableChartsForRemoteUser: "Создание диаграмм для удалённых пользователей"
+enableChartsForFederatedInstances: "Создание диаграмм для удалённых серверов"
+largeNoteReactions: "Показывать большие реакции на заметки"
+noteIdOrUrl: "ID или ссылка на заметку"
+video: "Видео"
+videos: "Видео"
+dataSaver: "Экономия трафика"
+horizontal: "Сбоку"
+youFollowing: "Подписки"
 _achievements:
   earnedAt: "Разблокировано в"
   _types:
@@ -1469,32 +1525,6 @@ _time:
   minute: "мин"
   hour: "ч"
   day: "сут"
-_tutorial:
-  title: "Как пользоваться Misskey"
-  step1_1: "Добро пожаловать!"
-  step1_2: "Эта страница называется «лента». Здесь будут появляться «заметки»: ваши личные и тех, на кого вы «подписаны». Они будут располагаться в порядке времени их появления."
-  step1_3: "Правда, ваша лента пока пуста. Она начнёт заполняться, когда вы будете писать свои заметки и подписываться на других."
-  step2_1: "Давайте, заполним профиль, прежде чем начать писать заметки и подписываться на других."
-  step2_2: "То, что вы расскажете в профиле, поможет лучше вас узнать, а значит, многим будет легче присоединиться — вы скорее получите новых подписчиков и читателей."
-  step3_1: "Успешно заполнили профиль?"
-  step3_2: "Что ж, теперь самое время опубликовать заметку. Если нажать вверху страницы на изображение карандаша, появится форма для текста."
-  step3_3: "Напишите в неё, что хотите, и нажмите на кнопку в правом верхнем углу."
-  step3_4: "Ничего не приходит в голову? Как насчёт: «Я новенький, пока осваиваюсь в Misskey»?"
-  step4_1: "С написанием первой заметки покончено?"
-  step4_2: "Отлично, теперь она должна появиться в вашей ленте."
-  step5_1: "А теперь самое время немного оживить ленту, подписавшись на других."
-  step5_2: "На странице «{featured}» собраны популярные сегодня заметки, читая которые, вы можете найти кого-то вам интересного, а на странице «{explore}» можно посмотреть, кто популярен у остальных."
-  step5_3: "Чтобы подписаться на кого-нибудь, щёлкните по его аватару и в открывшемся профиле нажмите кнопку «Подписаться»."
-  step5_4: "Некоторые пользователи (около их имени «висит замок») вручную подтверждают чужие подписки. Так что иногда подписка начинает работать не сразу.\n"
-  step6_1: "Если теперь в ленте видны и чужие заметки, значит у вас получилось."
-  step6_2: "Здесь можно непринуждённо выразить свои чувства к чьей-то заметке, отметив «реакцию» под ней."
-  step6_3: "Отмечайте реакции, нажмая на символ «+» под заметкой и выбирая значок по душе."
-  step7_1: "На этом вводный урок по использованию Misskey закончен. Спасибо, что прошли его до конца!"
-  step7_2: "Хотите изучить Misskey глубже — добро пожаловать в раздел «{help}»."
-  step7_3: "Приятно вам провести время с Misskey🚀"
-  step8_1: "Ах, да, не хотите ли включить push-уведомления?"
-  step8_2: "С push-уведомлениями вы будете в курсе репостов, ответов, реакций и всего такого, даже когда закрыли Misskey."
-  step8_3: "Эту настройку вы всегда сможете поменять"
 _2fa:
   alreadyRegistered: "Двухфакторная аутентификация уже настроена."
   registerTOTP: "Начните настраивать приложение-аутентификатор"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 7c74060158884013b88d59cc421dc1a4c63a6b58..3e96119389df68c8b1e864bb40641d397b7c3019 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -261,12 +261,12 @@ nUsersRead: "prečítané {n} používateľmi"
 agreeTo: "Súhlasím s {0}"
 agreeBelow: "Súhlasím s nasledovným"
 basicNotesBeforeCreateAccount: "Základné bezpečnostné opatrenia"
-tos: "Podmienky používania"
 start: "Začať"
 home: "Domov"
 remoteUserCaution: "Tieto informácie nemusia byť aktuálne, keďže používateľ je na vzdialenom serveri."
 activity: "Aktivita"
 images: "Obrázky"
+image: "Obrázky"
 birthday: "Dátum narodenia"
 yearsOld: "{age} rokov"
 registeredDate: "Dátum registrácie"
@@ -917,6 +917,8 @@ remindMeLater: "Pripomenúť neskôr"
 didYouLikeMisskey: "Páči sa vám Misskey?"
 pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím, prispejte, aby sme ho mohli ďalej rozvíjať!"
 color: "Farba"
+horizontal: "Strana"
+youFollowing: "Sledované"
 _role:
   priority: "Priorita"
   _priority:
@@ -1146,32 +1148,6 @@ _time:
   minute: "min"
   hour: "hod"
   day: "dní"
-_tutorial:
-  title: "Ako používať Misskey"
-  step1_1: "Vitajte!"
-  step1_2: "Táto stránka sa volá \"časová os\". Zobrazuje chronologicky zoradené \"poznámky\" od ľudí, ktorých sledujete."
-  step1_3: "Vaša časová os je teraz prázdna pretože ste nepridali žiadne poznámky ani nikoho zatiaľ nesledujete."
-  step2_1: "Podˇme dokončiť nastavenia vášho profilu pred napísaním poznámky alebo sledovaním niekoho."
-  step2_2: "Poskytnutím informácií o vás uľahčíte ostatným, či chcú vidieť alebo sledovať vaše poznámky."
-  step3_1: "Dokončili ste nastavovanie svojho profilu?"
-  step3_2: "Poďme vyskúšať napísať poznámku. Môžete to spraviť stlačením ikony ceruzky na vrchu obrazovky."
-  step3_3: "Vyplňte polia a stlačte tlačítko vpravo hore."
-  step3_4: "Nemáte čo povedať? Skúste \"len si nastavujem môj msky\"!"
-  step4_1: "Napísali ste svoju prvú poznámku?"
-  step4_2: "Hurá! Teraz by vaša prvá poznámka mala byť na vašej časovej osi."
-  step5_1: "Teraz skúsme oživiť časovú os sledovaním nejakých ľudí."
-  step5_2: "{featured} zobrazí populárne poznámku na tomto serveri. {explore} môžete objavovať populárnych používateľov. Skúste tam nájsť ľudí, ktorých by ste radi sledovali!"
-  step5_3: "Ak chcete sledovať ďalších používateľov, kliknite na ich ikonu a stlačte tlačidlo \"Sledovať\" na ich profile."
-  step5_4: "Ak má niektorý používateľ ikonu zámku vedľa svojho mena, znamená to, že môže trvať určitý čas, kým daný používateľ schváli vašu žiadosť o sledovanie."
-  step6_1: "Teraz by ste mali vidieť poznámky ďalších používateľov na svojej časovej osi."
-  step6_2: "Môžete dať \"reakcie\" na poznámky ďalších ľudí ako rýchlu odpoveď."
-  step6_3: "Reakciu pridáte kliknutím na \"+\" niekoho poznámke a vybratím emoji, ktorou chcete reagovať."
-  step7_1: "Gralujeme! Dokončili ste základného sprievodcu Misskey."
-  step7_2: "Ak sa chcete naučiť viac o Misskey, skúste sekciu {help}."
-  step7_3: "A teraz, veľa šťastia, bavte sa s Misskey! 🚀"
-  step8_1: "A nakoniec, prečo si neaktivovať push oznámenia?"
-  step8_2: "Vďaka push notifikáciám sa dozviete o reakciách, sledovaniach a zmienkach, aj keď Misskey nie je otvorené."
-  step8_3: "Nastavenia notifikácií môžete neskôr zmeniť."
 _2fa:
   alreadyRegistered: "Už ste zaregistrovali 2-faktorové autentifikačné zariadenie."
   step1: "Najprv si nainštalujte autentifikačnú aplikáciu (napríklad {a} alebo {b}) na svoje zariadenie."
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 6369adb454475a7c88a9025ce25c4e9b9d7712b5..7a00e3fcea09911b382380863e528e52463f0673 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -256,11 +256,11 @@ noMoreHistory: "Det finns ingen mer historik"
 startMessaging: "Starta en chatt"
 nUsersRead: "läst av {n}"
 agreeTo: "Jag accepterar {0}"
-tos: "Användarvillkor"
 home: "Hem"
 remoteUserCaution: "Då denna användaren kommer från en fjärrinstans, kan informationen visad vara ofullständig."
 activity: "Aktivitet"
 images: "Bilder"
+image: "Bilder"
 birthday: "Födelsedag"
 yearsOld: "{age} år gammal"
 registeredDate: "Gick med"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index a863103e2cb59897346975dcfedd5d9668b3c759..d1f87dbfdb2b71f36928016272960d966013c273 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -18,8 +18,9 @@ enterUsername: "ใส่ชื่อผู้ใช้"
 renotedBy: "รีโน้ตโดย {ผู้ใช้}"
 noNotes: "ไม่มีโน้ต"
 noNotifications: "ไม่มีการแจ้งเตือน"
-instance: "ตัวอย่าง"
+instance: "อินสแตนซ์"
 settings: "การตั้งค่า"
+notificationSettings: "ตั้งค่าการแจ้งเตือน"
 basicSettings: "การตั้งค่าพื้นฐาน"
 otherSettings: "การตั้งค่าอื่นๆ"
 openInWindow: "เปิดในหน้าต่าง"
@@ -99,7 +100,7 @@ followRequestPending: "กำลังรอดำเนินการร้อ
 enterEmoji: "ใส่อีโมจิ"
 renote: "รีโน้ต"
 unrenote: "เลิกรีโน้ต"
-renoted: "รีโน้ตเอาไว้"
+renoted: "รีโน้ตแล้วนะ"
 cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ"
 cantReRenote: "ไม่สามารถรีโน้ตเอาไว้ใหม่ได้นะ"
 quote: "อ้างคำพูด"
@@ -262,14 +263,16 @@ noMoreHistory: "ในนั้นไม่มีประวัติอีก
 startMessaging: "เริ่มการสนทนา"
 nUsersRead: "อ่านโดย {n}"
 agreeTo: "ฉันยอมรับที่จะ {0}"
+agree: "ยอมรับ"
 agreeBelow: "ฉันยอมรับถึงด้านล่าง"
 basicNotesBeforeCreateAccount: "หมายเหตุสำคัญ"
-tos: "ข้อกำหนดและเงื่อนไข"
+termsOfService: "เงื่อนไขการให้บริการ"
 start: "เริ่มต้น​ใช้งาน​"
 home: "หน้าแรก"
 remoteUserCaution: "เนื่องจากผู้ใช้งานรายนี้นั้น มาจากอินสแตนซ์ระยะไกล ข้อมูลที่แสดงดังกล่าวนั้นอาจจะไม่สมบูรณ์ก็ได้นะ"
 activity: "กิจกรรม"
 images: "รูปภาพ"
+image: "รูปภาพ"
 birthday: "วันเกิด"
 yearsOld: "{age} ปี"
 registeredDate: "วันที่สมัครสมาชิก"
@@ -467,12 +470,14 @@ enableAdvancedMfm: "เปิดใช้งาน MFM ขั้นสูง"
 enableAnimatedMfm: "เปิดการใช้งาน MFM ด้วยแอนิเมชั่น"
 doing: "กำลังประมวลผล......"
 category: "หมวดหมู่"
-tags: "แท็ก"
+tags: "นามแฝง"
 docSource: "ที่มาของเอกสารนี้"
 createAccount: "สร้างบัญชี"
 existingAccount: "บัญชีที่มีอยู่"
 regenerate: "สร้างอีกครั้ง"
 fontSize: "ขนาดตัวอักษร"
+mediaListWithOneImageAppearance: "ความสูงของลิสต์สื่อจะต้องมีรูปภาพเดียวเท่านั้น"
+limitTo: "จำกัดไว้ที่ {x}"
 noFollowRequests: "คุณไม่มีคำขอติดตามที่รอดำเนินการ"
 openImageInNewTab: "เปิดรูปภาพในแท็บใหม่"
 dashboard: "หน้ากระดานหลัก"
@@ -671,7 +676,7 @@ notesCount: "จำนวนของโน้ต"
 repliesCount: "จำนวนการตอบกลับที่ส่ง"
 renotesCount: "จำนวนรีโน้ตที่ส่ง"
 repliedCount: "จำนวนของการตอบกลับที่ได้รับ"
-renotedCount: "จำนวนรีโน้ตที่ได้รับ"
+renotedCount: "จำนวนรีโน้ตที่ได้รับแล้ว"
 followingCount: "จำนวนบัญชีที่ติดตาม"
 followersCount: "จำนวนผู้ติดตาม"
 sentReactionsCount: "จำนวนปฏิกิริยาที่ส่ง"
@@ -698,6 +703,8 @@ contact: "ติดต่อ"
 useSystemFont: "ใช้ฟอนต์เริ่มต้นของระบบ"
 clips: "คลิป"
 experimentalFeatures: "ฟังก์ชั่นทดสอบ"
+experimental: "ทดลอง"
+thisIsExperimentalFeature: "นี่คือฟีเจอร์ทดลองนะค่ะ ฟังก์ชันการทำงานบางอย่างอาจเปลี่ยนแปลงได้ และอาจไม่ทำงานหรือไม่เสถียรตามที่ตั้งใจไว้นะ"
 developer: "สำหรับนักพัฒนา"
 makeExplorable: "ทำให้บัญชีมองเห็นใน \"สำรวจ\""
 makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน \"สำรวจ\" นะ"
@@ -904,6 +911,7 @@ remoteOnly: "รีโมทเท่านั้น"
 failedToUpload: "การอัปโหลดล้มเหลว"
 cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น NSFW"
 cannotUploadBecauseNoFreeSpace: "การอัปโหลดนั้นล้มเหลวเนื่องจากไม่มีความจุของไดรฟ์"
+cannotUploadBecauseExceedsFileSizeLimit: "ไม่สามารถอัปโหลดไฟล์นี้ได้แล้วเนื่องจากเกินขีดจำกัดของขนาดไฟล์แล้ว"
 beta: "เบต้า"
 enableAutoSensitive: "ทำเครื่องหมาย NSFW อัตโนมัติ"
 enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่องหากเป็นไปได้ แม้ว่าตัวเลือกนี้จะถูกปิดใช้งาน แต่ก็สามารถเปิดใช้งานได้ทั้งอินสแตนซ์นี้"
@@ -937,6 +945,7 @@ didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
 pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
 roles: "บทบาท"
 role: "บทบาท"
+noRole: "ไม่พบบทบาท"
 normalUser: "ผู้ใช้มาตรฐาน"
 undefined: "ไม่ได้กำหนด"
 assign: "กำหนด"
@@ -946,6 +955,10 @@ manageCustomEmojis: "จัดการอีโมจิแบบกำหน
 youCannotCreateAnymore: "คุณถึงขีดจํากัดการสร้างแล้วนะ"
 cannotPerformTemporary: "ไม่สามารถใช้การได้ชั่วคราว"
 cannotPerformTemporaryDescription: "การดําเนินการนี้ไม่สามารถดําเนินการได้ชั่วคราว เนื่องจากเกินขีดจํากัดการดําเนินการ กรุณารอสักครู่แล้วลองใหม่อีกครั้งนะค่ะ"
+invalidParamError: "ข้อผิดพลาดพารามิเตอร์"
+invalidParamErrorDescription: "คำขอพารามิเตอร์ไม่ถูกต้อง สิ่งนี้มักจะเกิดจากข้อผิดพลาด แต่อาจเกิดจากอินพุตเกินขีดจำกัดของขนาดหรือที่คล้ายกัน"
+permissionDeniedError: "การดำเนินถูกปฏิเสธ"
+permissionDeniedErrorDescription: "บัญชีนี้ไม่มีสิทธิ์อนุญาตในการดำเนินการนี้"
 preset: "พรีเซ็ต"
 selectFromPresets: "เลือกจากการพรีเซ็ต"
 achievements: "ความสำเร็จ"
@@ -989,17 +1002,52 @@ enableChartsForFederatedInstances: "สร้างแผนภูมิข้
 showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
 largeNoteReactions: "ขยายรีแอคชั่นการแสดงผล"
 noteIdOrUrl: "โน้ต ID หรือ URL"
+video: "วีดีโอ"
+videos: "วีดีโอ"
+dataSaver: "ประหยัดข้อมูล"
 accountMigration: "การโยกย้ายบัญชี"
 accountMoved: "ผู้ใช้รายนี้ได้ย้ายไปยังบัญชีใหม่แล้ว:"
+accountMovedShort: "บัญชีนี้ถูกโอนย้ายไปแล้วค่ะ"
+operationForbidden: "ห้ามดำเนินการ"
 forceShowAds: "แสดงโฆษณาเสมอ"
+addMemo: "เพิ่มมีโม"
+editMemo: "แก้ไขมีโม"
+reactionsList: "ปฏิกิริยา"
+renotesList: "Renotes รีโน้ต"
+notificationDisplay: "การแจ้งเตือน"
+leftTop: "บนซ้าย"
+rightTop: "บนขวา"
+leftBottom: "ล่างซ้าย"
+rightBottom: "ล่างขวา"
+stackAxis: "ทิศทางการซ้อน"
+vertical: "แนวตั้ง"
+horizontal: "ด้านข้าง"
+position: "ตำแหน่ง"
+serverRules: "กฎของเซิฟเวอร์"
+pleaseConfirmBelowBeforeSignup: "โปรดยืนยันด้านล่างก่อนกำลังลงชื่อสมัครนะค่ะ"
+pleaseAgreeAllToContinue: "คุณต้องยอมรับทุกช่องตรงด้านบนเพื่อดำเนินการต่อค่ะ"
+continue: "ดำเนินการต่อ"
+preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว้"
+preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
+createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
+youFollowing: "ติดตามแล้ว"
+_serverRules:
+  description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
 _accountMigration:
-  moveTo: "ย้ายข้อมูลบัญชีนี้ไปยังบัญชีอีกหนึ่ง"
-  moveToLabel: "บัญชีที่จะย้ายไปที่:"
-  moveAccountDescription: "การกระทำนี้ไม่สามารถย้อนกลับได้นะ ขั้นตอนแรก ต้องสร้างนามแฝงสำหรับบัญชีนี้ในบัญชีที่คุณต้องการย้ายไป หลังจากนั้นแล้ว ป้อนบัญชีที่จะย้ายไปในรูปแบบดังต่อไปนี้: @person@instance.com"
   moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
+  moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น"
   moveFromLabel: "บัญชีที่จะย้ายจาก:"
   moveFromDescription: "สร้างนามแฝงสำหรับบัญชีที่จะย้ายจากบัญชีนี้ ถ้าหากคุณต้องการโอนผู้ติดตาม สิ่งนี้ต้องทำก่อนโอนก่อนนะค่ะ! หลังจากนั้น ป้อนบัญชีที่จะย้ายไปในรูปแบบต่อไปนี้: @person@instance.com"
+  moveTo: "ย้ายข้อมูลบัญชีนี้ไปยังบัญชีอีกหนึ่ง"
+  moveToLabel: "บัญชีที่จะย้ายไปที่:"
+  moveCannotBeUndone: "ไม่สามารถยกเลิกการโอนย้ายบัญชีได้"
+  moveAccountDescription: "การกระทำนี้ไม่สามารถย้อนกลับได้นะ ขั้นตอนแรก ต้องสร้างนามแฝงสำหรับบัญชีนี้ในบัญชีที่คุณต้องการย้ายไป หลังจากนั้นแล้ว ป้อนบัญชีที่จะย้ายไปในรูปแบบดังต่อไปนี้: @person@instance.com"
+  moveAccountHowTo: "หากต้องการย้ายข้อมูลก่อนอื่นให้สร้างชื่อแทนสำหรับบัญชีนี้ ในบัญชีที่จะต้องการย้ายไป\nหลังจากที่คุณสร้างนามแฝงนั้นแล้ว ให้ป้อนบัญชีที่ต้องการจะย้ายไปในรูปแบบดังต่อไปนี้: @username@server.example.com"
+  startMigration: "โอนย้าย"
   migrationConfirm: "ย้ายข้อมูลบัญชีนี้ไปที่ {account} จริงๆนะ เมื่อมีการเริ่มต้นแล้ว กระบวนการนี้จะไม่สามารถหยุดหรือนำกลับคืนมาได้ และคุณจะไม่สามารถใช้บัญชีนี้ในสถานะดั้งเดิมได้อีกต่อไป\n\nนอกจากนี้ เพื่อให้แน่ใจยืนยันว่าคุณได้สร้างนามแฝงในบัญชีที่จะย้ายข้อมูลนะค่ะ"
+  movedAndCannotBeUndone: "\nบัญชีนี้ถูกโอนย้ายไปแล้ว\nไม่สามารถย้อนกลับโอนย้ายข้อมูลได้"
+  postMigrationNote: "บัญชีนี้จะถูกเลิกติดตามบัญชีทั้งหมดที่กำลังติดตามภายใน 24 ชั่วโมงหลังจากการย้ายข้อมูลนั้นเสร็จสิ้น ทั้งจำนวนผู้ติดตามและผู้ติดตามนั้นจะกลายเป็นศูนย์ เพื่อหลีกเลี่ยงป้องกันไม่ให้ผู้ติดตามของคุณนั้นไม่สามารถเห็นโพสต์เฉพาะผู้ติดตามของบัญชีนี้ได้ แต่อย่างไรก็ตามแล้วพวกเขาจะยังคงติดตามบัญชีนี้ต่อไป"
+  movedTo: "บัญชีที่จะย้ายไปที่:"
 _achievements:
   earnedAt: "ได้รับเมื่อ"
   _types:
@@ -1171,6 +1219,9 @@ _achievements:
     _client30min:
       title: "พักผ่อนสักหน่อย"
       description: "ใช้เวลา 30 นาทีบน Misskey"
+    _client60min:
+      title: "ไม่มี \"Miss\" ใน Misskey นะค่ะ !"
+      description: "เปิด Misskey ค้างไว้แล้วอย่างน้อย 60 นาที"
     _noteDeletedWithin1min:
       title: "ไม่เป็นไร"
       description: "ลบโน้ตภายในหนึ่งนาทีหลังจากที่โพสต์"
@@ -1259,6 +1310,8 @@ _role:
   iconUrl: "ไอคอน URL"
   asBadge: "แสดงเป็นตรา"
   descriptionOfAsBadge: "ไอคอนของบทบาทนี้จะปรากฏถัดจากชื่อผู้ใช้ของผู้ใช้งานด้วยบทบาทนี้ถ้าหากเปิดใช้งาน"
+  isExplorable: "บทบาทไทม์ไลน์เป็นแบบสาธารณะ"
+  descriptionOfIsExplorable: "ไทม์ไลน์ของบทบาทนี้จะสามารถเข้าถึงได้แบบสาธารณะถ้าหากเปิดใช้งาน เส้นเวลาของบทบาทนั้นจะไม่ถูกเปิดเผยต่อสาธารณะ ถึงแม้ว่าจะไม่เปิดเผยต่อสาธารณะแม้แต่ว่า...จะตั้งค่าไว้ยังไงก็ตาม"
   displayOrder: "ตำแหน่ง"
   descriptionOfDisplayOrder: "ยิ่งตัวเลขสูง ตำแหน่ง UI ก็ยิ่งสูงขึ้นนะ"
   canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก"
@@ -1407,6 +1460,8 @@ _channel:
   following: "ติดตามแล้ว"
   usersCount: "{n} ผู้เข้าร่วม"
   notesCount: "{n} โน้ต"
+  nameAndDescription: "ชื่อและคำอธิบาย"
+  nameOnly: "ชื่อเท่านั้น"
 _menuDisplay:
   sideFull: "ด้านข้าง"
   sideIcon: "ด้านข้าง (ไอคอน)"
@@ -1524,32 +1579,6 @@ _time:
   minute: "นาที"
   hour: "ชั่วโมง"
   day: "วัน"
-_tutorial:
-  title: "วิธีการใช้งาน Misskey"
-  step1_1: "ยินดีต้อนรับค่ะ!"
-  step1_2: "หน้านี้เรียกว่า \"ไทม์ไลน์\" มันจะแสดง \"โน้ตย่อ\" ที่เรียงลำดับตามลำดับเวลาของคนที่คุณ \"ติดตาม\""
-  step1_3: "ไทม์ไลน์ของคุณนั้นว่างเปล่า เนื่องจากคุณยังไม่ได้โพสต์โน้ตย่อหรือไม่ได้ติดตามใครเลย"
-  step2_1: "มาตั้งค่าโปรไฟล์ของคุณให้เสร็จก่อนเขียนโน้ตย่อหรือติดตามใครก็ได้"
-  step2_2: "การให้ข้อมูลบางอย่างเกี่ยวกับตัวคุณนั้น จะทำให้ผู้อื่นทราบว่าต้องการดูโน้ตย่อของคุณหรือติดตามคุณได้ง่ายขึ้น"
-  step3_1: "ตั้งค่าโปรไฟล์ของคุณเสร็จแล้ว?"
-  step3_2: "จากนั้นลองโพสต์โน้ตกันต่อไป คุณสามารถทำได้โดยกดปุ่มที่มีไอคอนดินสอบนหน้าจอนะ"
-  step3_3: "กรอกโมดอลแล้วกดปุ่มด้านบนขวาเพื่อโพสต์"
-  step3_4: "ไม่มีอะไรจะพูดงั้นหรอ ลอง \"เพียงแค่ตั้งค่าว่า Misskey ของฉัน\"!"
-  step4_1: "เสร็จสิ้นการโพสต์โน้ตย่อแรกของคุณแล้วอย่างงั้นหรอ?"
-  step4_2: "ไชโย! ตอนนี้โน้ตย่อแรกของคุณได้ปรากฏบนไทม์ไลน์ของคุณแล้วนะ"
-  step5_1: "ตอนนี้ มาลองทำไทม์ไลน์เพิ่มเติมของคุณให้ดูมีชีวิตชีวามากขึ้นโดยการติดตามคนอื่น"
-  step5_2: "{featured} จะแสดงโน้ตยอดนิยมให้คุณเห็นในกรณีนี้ {explore} จะช่วยให้คุณค้นหาผู้ใช้ยอดนิยมได้ ลองหาคนที่คุณต้องการติดตามที่นั่นสิ!"
-  step5_3: "หากต้องการติดตามผู้ใช้รายอื่น ให้คลิกที่ไอคอนและกดปุ่ม \"ติดตาม\" บนโปรไฟล์ของพวกเขาได้เลยจ้า"
-  step5_4: "หากผู้ใช้รายอื่นมีไอคอนแม่กุญแจที่อยู่ข้างชื่อ อาจต้องใช้เวลาสักระยะกว่าที่ผู้ใช้รายนั้นจะอนุมัติคำขอติดตามของคุณ"
-  step6_1: "คุณสามารถเห็นโน้ตย่อของผู้ใช้รายอื่นบนไทม์ไลน์ของคุณได้แล้วตอนนี้"
-  step6_2: "คุณยังสามารถใส่ \"ปฏิกิริยา\" ลงในโน้ตของคนอื่นเพื่อตอบกลับได้อย่างรวดเร็ว"
-  step6_3: "หากต้องการแนบ \"ปฏิกิริยา\" ให้กดเครื่องหมาย \"+\" ในโน้ตของผู้ใช้รายอื่นแล้วเลือกอีโมจิที่คุณต้องการโต้ตอบด้วย"
-  step7_1: "ยินดีด้วยนะ! คุณได้เสร็จสิ้นการกวดวิชาพื้นฐานของ Misskey แล้ว"
-  step7_2: "ถ้าหากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับ Misskey ให้ลองใช้ส่วน {help}"
-  step7_3: "ตอนนี้ ถ้าอย่างนั้นก็ขอให้โชคดีและสนุกกับ Misskey! 🚀"
-  step8_1: "สุดท้ายนี้นายต้องการเปิดใช้งานการแจ้งเตือนแบบพุชหรือป่าว?"
-  step8_2: "การเปิดใช้งานสิ่งเหล่านี้ จะช่วยให้คุณนั้นได้รับการแจ้งเตือนสำหรับการกล่าวถึง การแสดงรีแอคชั่น การติดตาม ฯลฯ เป็นต้น ถึงแม้ว่าจะไม่ได้เปิด Misskey ก็ตาม"
-  step8_3: "คุณสามารถเปลี่ยนการตั้งค่านี้ในภายหลังได้ตลอดเวลานะ"
 _2fa:
   alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว"
   registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์"
@@ -1887,6 +1916,7 @@ _deck:
     channel: "แชนแนล"
     mentions: "พูดถึง"
     direct: "ไดเร็ค"
+    roleTimeline: "บทบาทไทม์ไลน์"
 _dialog:
   charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}"
   charactersBelow: "คุณกำลังใช้อักขระต่ำกว่าขีดจำกัดขั้นต่ำเลยนะ! ปัจจุบันอยู่ที่ {current} จาก {min}"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 895d2e1f12882f6c6a34087cdccb0f5127a40247..4f215bc9809e8c8432044f4c75cbe0bf96c3ee15 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -258,12 +258,12 @@ startMessaging: "Розпочати діалог"
 nUsersRead: "Прочитали {n}"
 agreeTo: "Я погоджуюсь з {0}"
 agreeBelow: "Я погоджуюся з наведеним нижче"
-tos: "Умови використання"
 start: "Розпочати"
 home: "Домівка"
 remoteUserCaution: "Інформація може бути неповною, оскільки це віддалений користувач."
 activity: "Активність"
 images: "Зображення"
+image: "Зображення"
 birthday: "День народження"
 yearsOld: "{age} років"
 registeredDate: "Приєднання"
@@ -899,6 +899,8 @@ achievements: "Досягнення"
 joinThisServer: "Зареєструватися на цьому сервері"
 exploreOtherServers: "Знайти інший сервер"
 letsLookAtTimeline: "Перегляд історії"
+horizontal: "Збоку"
+youFollowing: "Підписки"
 _achievements:
   earnedAt: "Відкрито"
   _types:
@@ -1330,31 +1332,6 @@ _time:
   minute: "Ñ…"
   hour: "г"
   day: "д"
-_tutorial:
-  title: "Як користуватись Misskey"
-  step1_1: "Ласкаво просимо!"
-  step1_2: "Ця сторінка має назву \"стрічка подій\". На ній з'являються записи користувачів на яких ви підписані."
-  step1_3: "Наразі ваша стрічка порожня, оскільки ви ще не написали жодної нотатки і не підписані на інших."
-  step2_1: "Перш ніж зробити запис або підписатись на когось, заповніть свій профіль."
-  step2_2: "Надання деякої інформації про себе допоможе іншим користувачам вирішити підписатись на вас."
-  step3_1: "Ви успішно налаштували свій обліковий запис?"
-  step3_2: "Наступним кроком є написання нотатки. Це можна зробити, натиснувши зображення олівця на екрані."
-  step3_3: "Після написання вмісту ви можете опублікувати його, натиснувши кнопку у верхньому правому куті форми."
-  step3_4: "Не знаєте що написати? Спробуйте \"Привіт, Misskey!\""
-  step4_1: "Ви розмістили свій перший запис?"
-  step4_2: "Ура! Ваш перший запис відображається на вашій стрічці подій."
-  step5_1: "Настав час оживити вашу стрічку подій підписавшись на інших користувачів."
-  step5_2: "{explore} допоможе вам знайти цікавих людей та підписатися на них."
-  step5_3: "Щоб підписатись на інших користувачів, нажміть на їхнє зображення, а потім на кнопку \"підписатись\"."
-  step5_4: "Якщо користувач має замок при імені, то йому потрібно буде вручну підтвердити вашу заявку на підписку."
-  step6_1: "Тепер ви повинні бачити записи інших користувачів на вашій стрічці подій."
-  step6_2: "Також ви можете швидко відповісти, або  \"відреагувати\" на записи інших користувачів."
-  step6_3: "Щоб \"відреагувати\", нажміть на знак плюс \"+\" на записі і виберіть емоджі яким ви хочете \"відреагувати\"."
-  step7_1: "Вітаю! Ви пройшли ознайомлення з Misskey."
-  step7_2: "Якщо ви хочете більше дізнатись про Misskey, зайдіть в розділ {help}."
-  step7_3: "Насолоджуйтесь Misskey! 🚀"
-  step8_1: "Наостанку, чи бажаєте ви ввімкнути push-сповіщення?"
-  step8_3: "Ви завжди можете змінити цей параметр пізніше."
 _2fa:
   alreadyRegistered: "Двофакторна автентифікація вже налаштована."
   step1: "Спершу встановіть на свій пристрій програму автентифікації (наприклад {a} або {b})."
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index ebd046b6d4dfda8802a4acf6c67df0cdabf0ff90..d620d99d806b2da5353d377e0eb40f7491458b2e 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -261,12 +261,12 @@ nUsersRead: "đọc bởi {n}"
 agreeTo: "Tôi đồng ý {0}"
 agreeBelow: "Đồng ý với nội dung dưới đây"
 basicNotesBeforeCreateAccount: "Những điều cơ bản cần chú ý "
-tos: "Điều khoản dịch vụ"
 start: "Bắt đầu"
 home: "Trang chính"
 remoteUserCaution: "Vì người dùng này ở máy chủ khác, thông tin hiển thị có thể không đầy đủ."
 activity: "Hoạt động"
 images: "Hình ảnh"
+image: "Hình ảnh"
 birthday: "Sinh nhật"
 yearsOld: "{age} tuổi"
 registeredDate: "Tham gia"
@@ -944,6 +944,8 @@ copyErrorInfo: "Sao chép thông tin lỗi"
 joinThisServer: "Đăng ký trên chủ máy này"
 exploreOtherServers: "Tìm chủ máy khác"
 letsLookAtTimeline: "Thá»­ xem Timeline"
+horizontal: "Thanh bên"
+youFollowing: "Đang theo dõi"
 _achievements:
   earnedAt: "Ngày thu nhận"
   _types:
@@ -1349,31 +1351,6 @@ _time:
   minute: "phút"
   hour: "giờ"
   day: "ngày"
-_tutorial:
-  title: "Cách dùng Misskey"
-  step1_1: "Xin chào!"
-  step1_2: "Trang này gọi là \"bảng tin\". Nó hiện \"tút\" từ những người mà bạn \"theo dõi\" theo thứ tự thời gian."
-  step1_3: "Bảng tin của bạn đang trống, bởi vì bạn chưa đăng tút nào hoặc chưa theo dõi ai."
-  step2_1: "Hãy hoàn thành việc thiết lập hồ sơ của bạn trước khi viết tút hoặc theo dõi bất kỳ ai."
-  step2_2: "Cung cấp một số thông tin giới thiệu bạn là ai sẽ giúp người khác dễ dàng biết được họ muốn đọc tút hay theo dõi bạn."
-  step3_1: "Hoàn thành thiết lập hồ sơ của bạn?"
-  step3_2: "Sau đó, hãy thử đăng một tút tiếp theo. Bạn có thể làm như vậy bằng cách nhấn vào nút có biểu tượng bút chì trên màn hình."
-  step3_3: "Nhập nội dung vào khung soạn thảo và nhấn nút đăng ở góc trên."
-  step3_4: "Chưa biết nói gì? Thử \"Tôi mới tham gia Misskey\"!"
-  step4_1: "Đăng xong tút đầu tiên của bạn?"
-  step4_2: "De! Tút đầu tiên của bạn đã hiện trên bảng tin."
-  step5_1: "Bây giờ, hãy thử làm cho bảng tin của bạn sinh động hơn bằng cách theo dõi những người khác."
-  step5_2: "{feature} sẽ hiển thị cho bạn các tút nổi bật trên máy chủ này. {explore} sẽ cho phép bạn tìm thấy những người dùng thú vị. Hãy thử tìm những người bạn muốn theo dõi ở đó!"
-  step5_3: "Để theo dõi những người dùng khác, hãy nhấn vào ảnh đại diện của họ và nhấn nút \"Theo dõi\" trên hồ sơ của họ."
-  step5_4: "Nếu người dùng khác có biểu tượng ổ khóa bên cạnh tên của họ, có thể mất một khoảng thời gian để người dùng đó phê duyệt yêu cầu theo dõi của bạn theo cách thủ công."
-  step6_1: "Bạn sẽ có thể xem tút của những người dùng khác trên bảng tin của mình ngay bây giờ."
-  step6_2: "Bạn cũng có thể đặt \"biểu cảm\" trên tút của người khác để phản hồi nhanh chúng."
-  step6_3: "Để đính kèm \"biểu cảm\", hãy nhấn vào dấu \"+\" trên tút của người dùng khác rồi chọn biểu tượng cảm xúc mà bạn muốn dùng."
-  step7_1: "Xin chúc mừng! Bây giờ bạn đã hoàn thành phần hướng dẫn cơ bản của Misskey."
-  step7_2: "Nếu bạn muốn tìm hiểu thêm về Misskey, hãy thử phần {help}."
-  step7_3: "Bây giờ, chúc may mắn và vui vẻ với Misskey! 🚀"
-  step8_1: "Cuối cùng, bạn hãy bật thông báo đẩy nha!"
-  step8_2: "Nhận thông báo đẩy bạn sẽ có thể thấy phản hồi, theo dõi, lượt nhắc được trong khi đóng Misskey"
 _2fa:
   alreadyRegistered: "Bạn đã đăng ký thiết bị xác minh 2 bước."
   passwordToTOTP: "Nhắn mật mã"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 62f6bc6c321582da1c73d6a064aafb27f35d345f..1fe82185beed692e4a0882b377b3b81c8e3db3c2 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -114,7 +114,7 @@ sensitive: "敏感内容"
 add: "添加"
 reaction: "回应"
 reactions: "回应"
-reactionSetting: "在选择器中显示的回应"
+reactionSetting: "在选择器中显示回应"
 reactionSettingDescription2: "拖动重新排序,单击删除,点击 + 添加。"
 rememberNoteVisibility: "保存上次设置的可见性"
 attachCancel: "删除附件"
@@ -167,7 +167,7 @@ searchWith: "搜索:{q}"
 youHaveNoLists: "列表为空"
 followConfirm: "你确定要关注{name}吗?"
 proxyAccount: "代理账户"
-proxyAccountDescription: "代理账户是在某些情况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该实例,因此将代之以代理账户。"
+proxyAccountDescription: "代理账户是在某些情况下充当用户的远程关注者的账户。 例如,当一个用户列出一个远程用户时,如果没有人跟随该列出的用户,则该活动将不会传递到该服务器,因此将代之以代理账户。"
 host: "主机名"
 selectUser: "选择用户"
 recipient: "收件人"
@@ -263,14 +263,16 @@ noMoreHistory: "没有更多的历史记录"
 startMessaging: "添加聊天"
 nUsersRead: "{n}人已读"
 agreeTo: "勾选则表示已阅读并同意{0}"
-agreeBelow: "同意以下观点"
+agree: "同意"
+agreeBelow: "同意以下内容"
 basicNotesBeforeCreateAccount: "基本注意事项"
-tos: "服务条款"
+termsOfService: "服务条款"
 start: "开始"
 home: "首页"
 remoteUserCaution: "由于此用户来自其它服务器,显示的信息可能不完整。"
 activity: "活动"
 images: "图片"
+image: "图片"
 birthday: "生日"
 yearsOld: "{age}岁"
 registeredDate: "注册于"
@@ -403,7 +405,7 @@ totpDescription: "使用认证应用输入一次性密码。"
 moderator: "监察员"
 moderation: "管理"
 nUsersMentioned: "{n} 被提到"
-securityKeyAndPasskey: "安全密钥/密码"
+securityKeyAndPasskey: "安全密钥或 Passkey"
 securityKey: "安全密钥"
 lastUsed: "最后使用:"
 lastUsedAt: "最后使用: {t}"
@@ -474,6 +476,8 @@ createAccount: "注册账户"
 existingAccount: "现有的账户"
 regenerate: "重新生成"
 fontSize: "字体大小"
+mediaListWithOneImageAppearance: "仅一张图片的媒体列表高度"
+limitTo: "上限为{x}"
 noFollowRequests: "没有关注申请"
 openImageInNewTab: "在新标签页中打开图片"
 dashboard: "管理面板"
@@ -613,7 +617,7 @@ testEmail: "邮件发送测试"
 wordMute: "文字屏蔽"
 regexpError: "正则表达式错误"
 regexpErrorDescription: "{tab} 屏蔽文字的第 {line} 行的正则表达式有错误:"
-instanceMute: "实例的屏蔽"
+instanceMute: "被屏蔽的服务器"
 userSaysSomething: "{name}说了什么,但是被屏蔽词过滤了"
 makeActive: "启用"
 display: "显示"
@@ -699,6 +703,8 @@ contact: "联系人"
 useSystemFont: "使用系统默认字体"
 clips: "便签"
 experimentalFeatures: "实验性功能"
+experimental: "实验性的"
+thisIsExperimentalFeature: "这是一项实验性功能。规范可能会变更,或者可能无法正常工作。"
 developer: "开发者"
 makeExplorable: "使账号可见。"
 makeExplorableDescription: "关闭时,账号不会显示在\"发现\"中。"
@@ -886,7 +892,7 @@ typeToConfirm: "输入 {x} 以确认操作。"
 deleteAccount: "删除账户"
 document: "文档"
 numberOfPageCache: "缓存页数"
-numberOfPageCacheDescription: "设置较高的值会更方便用户,但设备的负载和内存使用量会增加。"
+numberOfPageCacheDescription: "设置较高的值会更方便用户,但服务器负载和内存使用量会增加。"
 logoutConfirm: "是否确认登出?"
 lastActiveDate: "最后活跃时间"
 statusbar: "状态栏"
@@ -905,6 +911,7 @@ remoteOnly: "仅远程"
 failedToUpload: "上传失败"
 cannotUploadBecauseInappropriate: "因为可能含有不适宜的内容,无法上传。"
 cannotUploadBecauseNoFreeSpace: "因为已无可用空间,无法上传。"
+cannotUploadBecauseExceedsFileSizeLimit: "无法上传文件,超过文件大小限制。"
 beta: "测试"
 enableAutoSensitive: "自动 NSFW 识别"
 enableAutoSensitiveDescription: "如果可用,请使用机器学习在媒体上自动设置 NSFW 标志。即使关闭此功能,也可能会根据服务器自动设置。"
@@ -938,6 +945,7 @@ didYouLikeMisskey: "您喜欢Misskey吗?"
 pleaseDonate: "Misskey是{host}所使用的免费软件。为了今后也能够维持Misskey的开发,请在有余力的情况下进行捐助!"
 roles: "角色"
 role: "角色"
+noRole: "角色不存在"
 normalUser: "普通用户"
 undefined: "未定义"
 assign: "分配"
@@ -947,6 +955,10 @@ manageCustomEmojis: "管理自定义表情符号"
 youCannotCreateAnymore: "抱歉,您无法再创建更多了。"
 cannotPerformTemporary: "暂时不可用"
 cannotPerformTemporaryDescription: "因操作过于频繁,暂时不可用,请稍后再试。"
+invalidParamError: "参数错误"
+invalidParamErrorDescription: "请求参数出现问题。通常是因为bug造成的,但也可能是输入文字数量过多之类的原因。"
+permissionDeniedError: "操作被拒绝"
+permissionDeniedErrorDescription: "本账户没有执行该操作的权限。"
 preset: "預設值"
 selectFromPresets: "從預設值中選擇"
 achievements: "成就"
@@ -990,17 +1002,45 @@ enableChartsForFederatedInstances: "生成远程服务器的图表"
 showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
 largeNoteReactions: "使用大图标来显示回应"
 noteIdOrUrl: "帖子ID或URL"
+video: "视频"
+videos: "视频"
+dataSaver: "省流量模式"
 accountMigration: "账户迁移"
 accountMoved: "此用户已迁移账户"
+accountMovedShort: "此帐户已迁移"
+operationForbidden: "不允许此操作"
 forceShowAds: "总是显示广告"
+addMemo: "添加备注"
+editMemo: "编辑备注"
+reactionsList: "回应列表"
+renotesList: "转发列表"
+notificationDisplay: "显示通知"
+leftTop: "屏幕左上方"
+rightTop: "屏幕右上方"
+leftBottom: "屏幕左下方"
+rightBottom: "屏幕右下方"
+stackAxis: "堆叠方向"
+vertical: "纵向"
+horizontal: "横向"
+position: "位置"
+serverRules: "服务器规则"
+pleaseConfirmBelowBeforeSignup: "在这个服务器上注册账号前,请确认以下信息。"
+pleaseAgreeAllToContinue: "必须全部勾选「同意」才能够继续。"
+continue: "继续"
+preservedUsernames: "保留的用户名"
+createNoteFromTheFile: "从文件创建帖子"
+youFollowing: "正在关注"
+_serverRules:
+  description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
 _accountMigration:
-  moveTo: "把这个账户迁移到新的账户"
-  moveToLabel: "迁移后的账户"
-  moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
   moveFrom: "从别的账号迁移到此账户"
   moveFromLabel: "迁移前的账户"
   moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
+  moveTo: "把这个账户迁移到新的账户"
+  moveToLabel: "迁移后的账户"
+  moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
   migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。"
+  movedTo: "迁移后的账户"
 _achievements:
   earnedAt: "达成时间"
   _types:
@@ -1172,6 +1212,9 @@ _achievements:
     _client30min:
       title: "休息一下!"
       description: "启动客户端超过30分钟"
+    _client60min:
+      title: "Misskey重度依赖"
+      description: "启动客户端超过60分钟"
     _noteDeletedWithin1min:
       title: "欲言又止"
       description: "发帖后一分钟内就将其删除"
@@ -1260,6 +1303,8 @@ _role:
   iconUrl: "图标URL"
   asBadge: "作为徽章显示"
   descriptionOfAsBadge: "开启后,用户名旁边将会出现角色图标。"
+  isExplorable: "公开角色时间线"
+  descriptionOfIsExplorable: "打开后将公开角色时间线。如果角色不是公开的,就无法公开时间线。"
   displayOrder: "显示顺序"
   descriptionOfDisplayOrder: "数字越大,显示位置越靠前。"
   canEditMembersByModerator: "允许监察者编辑成员"
@@ -1527,32 +1572,6 @@ _time:
   minute: "分"
   hour: "小时"
   day: "æ—¥"
-_tutorial:
-  title: "Misskey的使用方法"
-  step1_1: "欢迎!"
-  step1_2: "这个页面叫做「时间线」,它会按照时间顺序显示所有你「关注」的人所发的「帖子」。"
-  step1_3: "如果你并没有发布任何帖子,也没有关注其他的人,你的时间线页面应当什么都没有显示。"
-  step2_1: "在您想要发帖或关注其他人之前,请先设置一下个人资料吧。"
-  step2_2: "如果别人能够更加的了解你,关注你的概率也会得到提升。"
-  step3_1: "已经设置完个人资料了吗?"
-  step3_2: "那么接下来,试着写一些什么东西来发布吧。你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
-  step3_3: "写完内容后,点击窗口右上方的按钮就可以投稿。"
-  step3_4: "不知道说些什么好吗?那就写下「Misskey我来啦!」这样的话吧。"
-  step4_1: "将你的话语发布出去了吗?"
-  step4_2: "太棒了!现在你可以在你的时间线中看到你刚刚发布的帖子了。"
-  step5_1: "接下来,关注其他人来使时间线更生动吧。"
-  step5_2: "{featured}将向您展示热门趋势的帖子。 {explore}将让您找到热门用户。 尝试关注您喜欢的人!"
-  step5_3: "要关注其他用户,请单击他的头像,然后在他的个人资料上按下“关注”按钮。"
-  step5_4: "如果用户的名称旁边有锁定图标,则该用户需要手动批准您的关注请求。"
-  step6_1: "现在,您将可以在时间线上看到其他用户的帖子。"
-  step6_2: "您还可以在其他人的帖子上进行「回应」,以快速做出简单回复。"
-  step6_3: "在他人的贴子上按下「+」图标,即可选择想要的表情来进行「回应」。"
-  step7_1: "对Misskey基本操作的简单介绍,就到此结束了。 辛苦了!"
-  step7_2: "如果你想了解更多有关Misskey的信息,请参见{help}。"
-  step7_3: "接下来,享受Misskey带来的乐趣吧🚀"
-  step8_1: "最后,您想要启用推送通知消息吗?"
-  step8_2: "通过接收推送通知消息,即使没开 Misskey,您也可以收到回应、关注、提及等的消息。"
-  step8_3: "您也可以稍后再更改通知设置。"
 _2fa:
   alreadyRegistered: "此设备已被注册"
   registerTOTP: "开始设置认证应用"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 09253db2d77df1ba8561c80618d14c624e3061f2..4073b9629d9b77fd17f9db13e871c5a1f6b570ea 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -2,7 +2,7 @@
 _lang_: "繁體中文"
 headlineMisskey: "貼文連繫網路"
 introMisskey: "歡迎! Misskey是一個開放原始碼且去中心化的社群網路。\n透過「貼文」分享周邊新鮮事,並告訴其他人您的想法!📡\n透過「反應」功能,對大家的貼文表達情感!👍\n一起來探索這個新的世界吧!🚀"
-poweredByMisskeyDescription: "{name}是使用開放原始碼平台<b>Misskey</b>的服務之一(稱為 Misskey 實例)。\n"
+poweredByMisskeyDescription: "{name}是使用開放原始碼平台<b>Misskey</b>的服務之一(稱為 Misskey 伺服器)。\n"
 monthAndDay: "{month}月 {day}日"
 search: "搜尋"
 notifications: "通知"
@@ -18,7 +18,7 @@ enterUsername: "輸入使用者名稱"
 renotedBy: "{user} 轉發了"
 noNotes: "無貼文。"
 noNotifications: "沒有通知"
-instance: "實例"
+instance: "伺服器"
 settings: "設定"
 notificationSettings: "通知選項"
 basicSettings: "基本設定"
@@ -86,7 +86,7 @@ somethingHappened: "發生錯誤"
 retry: "重試"
 pageLoadError: "載入頁面失敗"
 pageLoadErrorDescription: "這通常是因為網路錯誤或是瀏覽器快取殘留的原因。請先清除瀏覽器快取,稍後再重試"
-serverIsDead: "伺服器沒有回應。請稍等片刻,然後重試。"
+serverIsDead: "伺服器沒有回應。請稍等片刻再試。"
 youShouldUpgradeClient: "請重新載入以使用新版本的客戶端顯示此頁面"
 enterListName: "輸入清單名稱"
 privacy: "隱私"
@@ -96,7 +96,7 @@ follow: "追隨"
 followRequest: "追隨請求"
 followRequests: "追隨請求"
 unfollow: "取消追隨"
-followRequestPending: "追隨許可批准中"
+followRequestPending: "追隨許可待批准"
 enterEmoji: "輸入表情符號"
 renote: "轉發"
 unrenote: "取消轉發"
@@ -150,11 +150,11 @@ cacheRemoteFiles: "快取遠端檔案"
 cacheRemoteFilesDescription: "禁用此設定會停止遠端檔案的緩存,從而節省儲存空間,但資料會因直接連線從而產生額外連接數據。"
 flagAsBot: "此使用者是機器人"
 flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整Misskey內部系統將本帳戶識別為機器人"
-flagAsCat: "此使用者是貓"
+flagAsCat: "喵~~~~~~~~~~~~~~!!!!!!!!!!!!"
 flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示"
 flagShowTimelineReplies: "在時間軸上顯示貼文的回覆"
 flagShowTimelineRepliesDescription: "啟用時,時間線除了顯示用戶的貼文以外,還會顯示用戶對其他貼文的回覆。"
-autoAcceptFollowed: "自動追隨中使用者的追隨請求"
+autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求"
 addAccount: "添加帳戶"
 reloadAccountsList: "更新帳戶清單的資訊"
 loginFailed: "登入失敗"
@@ -167,7 +167,7 @@ searchWith: "搜尋: {q}"
 youHaveNoLists: "你沒有任何清單"
 followConfirm: "你真的要追隨{name}嗎?"
 proxyAccount: "代理帳戶"
-proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追蹤該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追蹤。"
+proxyAccountDescription: "代理帳戶是在某些情況下充當其他伺服器用戶的帳戶。例如,當使用者將一個來自其他伺服器的帳戶放在列表中時,由於沒有其他使用者追隨該帳戶,該指令不會傳送到該伺服器上,因此會由代理帳戶追隨。"
 host: "主機"
 selectUser: "選取使用者"
 recipient: "收件人"
@@ -182,7 +182,7 @@ charts: "圖表"
 perHour: "每小時"
 perDay: "每日"
 stopActivityDelivery: "停止發送活動"
-blockThisInstance: "封鎖此實例"
+blockThisInstance: "封鎖此伺服器"
 operations: "操作"
 software: "軟體"
 version: "版本"
@@ -193,15 +193,15 @@ jobQueue: "佇列"
 cpuAndMemory: "CPU及記憶體用量"
 network: "網路"
 disk: "硬碟"
-instanceInfo: "實例資訊"
+instanceInfo: "伺服器資訊"
 statistics: "統計"
 clearQueue: "清除佇列"
 clearQueueConfirmTitle: "確定要清除佇列嗎?"
 clearQueueConfirmText: "未發佈的貼文將不會發佈。您通常不需要確認。"
 clearCachedFiles: "清除快取資料"
 clearCachedFilesConfirm: "確定要清除所有遠端暫存資料嗎?"
-blockedInstances: "已封鎖的實例"
-blockedInstancesDescription: "請逐行輸入需要封鎖的實例。已封鎖的實例將無法與本實例進行通訊。"
+blockedInstances: "已封鎖的伺服器"
+blockedInstancesDescription: "請逐行輸入需要封鎖的伺服器。已封鎖的伺服器將無法與本伺服器進行通訊。"
 muteAndBlock: "靜音和封鎖"
 mutedUsers: "已靜音用戶"
 blockedUsers: "已封鎖用戶"
@@ -224,8 +224,8 @@ all: "全部"
 subscribing: "訂閱中"
 publishing: "直播中"
 notResponding: "沒有回應"
-instanceFollowing: "追蹤實例"
-instanceFollowers: "追蹤實例"
+instanceFollowing: "追隨的伺服器"
+instanceFollowers: "伺服器的追隨者"
 instanceUsers: "用戶"
 changePassword: "修改密碼"
 security: "安全性"
@@ -263,14 +263,16 @@ noMoreHistory: "沒有更多歷史紀錄"
 startMessaging: "開始聊天"
 nUsersRead: "{n}人已讀"
 agreeTo: "我同意{0}"
+agree: "同意"
 agreeBelow: "同意以下內容"
 basicNotesBeforeCreateAccount: "基本注意事項"
-tos: "使用條款"
+termsOfService: "服務條款"
 start: "開始"
 home: "首頁"
 remoteUserCaution: "由於該使用者來自遠端實例,因此資訊可能非即時的。"
 activity: "å‹•æ…‹"
 images: "圖片"
+image: "圖片"
 birthday: "生日"
 yearsOld: "{age}æ­²"
 registeredDate: "註冊日期"
@@ -318,8 +320,8 @@ unwatch: "取消追隨"
 accept: "接受"
 reject: "拒絕"
 normal: "正常"
-instanceName: "實例名稱"
-instanceDescription: "實例說明"
+instanceName: "伺服器名稱"
+instanceDescription: "伺服器介紹"
 maintainerName: "管理員名稱"
 maintainerEmail: "管理員郵箱"
 tosUrl: "服務條款URL"
@@ -390,7 +392,7 @@ recentlyUpdatedUsers: "最近發文的使用者"
 recentlyRegisteredUsers: "新加入使用者"
 recentlyDiscoveredUsers: "最近發現的使用者"
 exploreUsersCount: "有{count}個使用者"
-exploreFediverse: "探索聯邦世界"
+exploreFediverse: "探索聯邦宇宙"
 popularTags: "熱門標籤"
 userList: "使用者清單"
 about: "關於"
@@ -474,7 +476,9 @@ createAccount: "建立帳戶"
 existingAccount: "現有帳戶"
 regenerate: "再生"
 fontSize: "字體大小"
-noFollowRequests: "沒有跟隨您的請求"
+mediaListWithOneImageAppearance: "僅1枚圖片的媒體列表高度"
+limitTo: "上限為{x}"
+noFollowRequests: "沒有追隨您的請求"
 openImageInNewTab: "於新分頁中開啟圖片"
 dashboard: "儀表板"
 local: "本地"
@@ -483,7 +487,7 @@ total: "合計"
 weekOverWeekChanges: "與上週相比"
 dayOverDayChanges: "與前一日相比"
 appearance: "外觀"
-clientSettings: "用戶端設定"
+clientSettings: "客戶端設定"
 accountSettings: "帳戶設定"
 promotion: "推廣"
 promote: "推廣"
@@ -543,8 +547,8 @@ disablePagesScript: "停用頁面的AiScript腳本"
 updateRemoteUser: "更新遠端使用者資訊"
 deleteAllFiles: "刪除所有檔案"
 deleteAllFilesConfirm: "要删除所有檔案嗎?"
-removeAllFollowing: "解除所有追蹤"
-removeAllFollowingDescription: "解除{host}所有的追蹤。在實例不再存在時執行。"
+removeAllFollowing: "解除所有追隨"
+removeAllFollowingDescription: "解除{host}所有的追隨。在伺服器不再存在時執行。"
 userSuspended: "該使用者已被停用"
 userSilenced: "該用戶已被禁言。"
 yourAccountSuspendedTitle: "帳戶已被凍結"
@@ -556,6 +560,7 @@ accountDeletedDescription: "這個帳戶已被刪除。"
 menu: "選單"
 divider: "分割線"
 addItem: "新增項目"
+rearrange: "排序方式"
 relays: "中繼"
 addRelay: "新增中繼"
 inboxUrl: "收件夾URL"
@@ -673,8 +678,8 @@ repliesCount: "回覆數量"
 renotesCount: "轉發數量"
 repliedCount: "回覆數量"
 renotedCount: "轉發次數"
-followingCount: "正在跟隨的用戶數量"
-followersCount: "跟隨者數量"
+followingCount: "正在追隨的用戶數量"
+followersCount: "追隨者數量"
 sentReactionsCount: "反應發送次數"
 receivedReactionsCount: "收到反應次數"
 pollVotesCount: "已統計的投票數"
@@ -699,6 +704,8 @@ contact: "聯絡人"
 useSystemFont: "使用系統預設的字型"
 clips: "摘錄"
 experimentalFeatures: "實驗中的功能"
+experimental: "實驗性"
+thisIsExperimentalFeature: "這是實驗性的功能。可能會有變更規格和不能正常動作的可能性。"
 developer: "開發者"
 makeExplorable: "使自己的帳戶能夠在「探索」頁面中顯示"
 makeExplorableDescription: "如果關閉,帳戶將不會被顯示在「探索」頁面中。"
@@ -734,8 +741,8 @@ registry: "登錄表"
 closeAccount: "停用帳戶"
 currentVersion: "目前版本"
 latestVersion: "最新版本"
-youAreRunningUpToDateClient: "您所使用的用戶端已經是最新的。"
-newVersionOfClientAvailable: "新版本的用戶端可用。"
+youAreRunningUpToDateClient: "您所使用的客戶端已經是最新的。"
+newVersionOfClientAvailable: "新版本的客戶端可用。"
 usageAmount: "使用量"
 capacity: "容量"
 inUse: "已使用"
@@ -821,7 +828,7 @@ pubSub: "Pub/Sub 帳戶"
 lastCommunication: "最近的通信"
 resolved: "已解決"
 unresolved: "未解決"
-breakFollow: "移除追蹤者"
+breakFollow: "解除追隨者"
 breakFollowConfirm: "確定要取消被追隨嗎?"
 itsOn: "已開啟"
 itsOff: "已關閉"
@@ -905,6 +912,7 @@ remoteOnly: "僅限遠端"
 failedToUpload: "上傳失敗"
 cannotUploadBecauseInappropriate: "由於判定可能包含不適當的內容,因此無法上傳。"
 cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有可用空間,因此無法上傳。"
+cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制,無法上傳。"
 beta: "Beta"
 enableAutoSensitive: "自動NSFW判定"
 enableAutoSensitiveDescription: "如果可用,請利用機器學習在媒體上自動設置 NSFW 旗標。 即使關閉此功能,依實例而定也可能會自動設置。"
@@ -938,6 +946,7 @@ didYouLikeMisskey: "您是否喜愛Misskey呢?"
 pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!"
 roles: "角色"
 role: "角色"
+noRole: "沒有角色"
 normalUser: "一般使用者"
 undefined: "未定義"
 assign: "指派"
@@ -947,6 +956,10 @@ manageCustomEmojis: "管理自訂表情符號"
 youCannotCreateAnymore: "您無法再建立更多了。"
 cannotPerformTemporary: "暫時無法進行"
 cannotPerformTemporaryDescription: "由於超過操作次數限制,暫時無法進行。請過一段時間之後再嘗試。"
+invalidParamError: "參數錯誤"
+invalidParamErrorDescription: "請求參數有問題。通常是bug造成的,但也有輸入的字元數過多之類的可能性。"
+permissionDeniedError: "操作被拒絕"
+permissionDeniedErrorDescription: "本帳號沒有執行這個操作的權限。"
 preset: "預設值"
 selectFromPresets: "從預設值中選擇"
 achievements: "成就"
@@ -990,17 +1003,57 @@ enableChartsForFederatedInstances: "生成遠端伺服器的圖表"
 showClipButtonInNoteFooter: "將摘錄添加至貼文"
 largeNoteReactions: "將貼文的反應放大顯示"
 noteIdOrUrl: "貼文ID或URL"
+video: "影片"
+videos: "影片"
+dataSaver: "數據節省模式"
 accountMigration: "遷移帳戶"
 accountMoved: "這個使用者已遷移至新的帳戶:"
+accountMovedShort: "此帳戶已遷移"
+operationForbidden: "不允許此操作"
 forceShowAds: "總是顯示廣告"
+addMemo: "新增備註"
+editMemo: "編輯備註"
+reactionsList: "反應列表"
+renotesList: "轉發貼文列表"
+notificationDisplay: "通知的顯示"
+leftTop: "左上"
+rightTop: "右上"
+leftBottom: "左下"
+rightBottom: "右下"
+stackAxis: "堆疊方向"
+vertical: "縱向"
+horizontal: "側向"
+position: "位置"
+serverRules: "伺服器規則"
+pleaseConfirmBelowBeforeSignup: "在本伺服器註冊之前,請確認下列事項。"
+pleaseAgreeAllToContinue: "必須全部勾選「同意」才能繼續。"
+continue: "繼續"
+preservedUsernames: "保留的使用者名稱"
+preservedUsernamesDescription: "換行列舉要保留的使用者名稱。此處指定的使用者名稱,在建立帳戶時無法使用,但由管理者所建立的帳戶不受此限。此外,既有的帳戶也不受影響。"
+createNoteFromTheFile: "由此檔案建立貼文"
+archive: "封存"
+channelArchiveConfirmTitle: "要封存{name}嗎?"
+channelArchiveConfirmDescription: "封存以後,在頻道列表與搜索結果中不會顯示,也無法發布新的貼文。"
+thisChannelArchived: "這個頻道已被封存。"
+displayOfNote: "顯示貼文"
+youFollowing: "關注中"
+_serverRules:
+  description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
 _accountMigration:
+  moveFrom: "從其他帳戶遷移到這個帳戶"
+  moveFromSub: "為另一個帳戶建立別名"
+  moveFromLabel: "要遷移過來的帳戶:"
+  moveFromDescription: "如果你想把追隨者從別的帳戶遷移過來,必須先在這裡建立別名。請務必在執行遷移之前建立別名!請像這樣輸入要遷移的帳戶:@person@instance.com"
   moveTo: "將這個帳戶遷移至新的帳戶"
   moveToLabel: "要遷移到的帳戶:"
+  moveCannotBeUndone: "一旦遷移帳戶,就無法取消。"
   moveAccountDescription: "這個操作不可撤銷。首先,請確認已在要遷移到的帳戶中為這個帳戶建立了一個別名。建立別名之後,像這樣輸入你要遷移到的帳戶:@person@instance.com"
-  moveFrom: "從其他帳戶遷移到這個帳戶"
-  moveFromLabel: "要遷移過來的帳戶:"
-  moveFromDescription: "如果你想把跟隨者從別的帳戶遷移過來,必須先在這裡建立別名。請務必在執行遷移之前建立別名!請像這樣輸入要遷移的帳戶:@person@instance.com"
+  moveAccountHowTo: "要遷移帳戶,首先要在目標帳戶中為此帳戶建立一個別名。\n 建立別名後,像這樣輸入目標帳戶:@username@server.example.com"
+  startMigration: "遷移"
   migrationConfirm: "確定要將這個帳戶遷移至 {account} 嗎?一旦遷移就無法撤銷,也就無法以原來的狀態使用這個帳戶。\n另外,請確認在要遷移到的帳戶已經建立了一個別名。"
+  movedAndCannotBeUndone: "帳戶已遷移。\n遷移無法撤消。"
+  postMigrationNote: "在遷移操作後的24小時之後解除此帳戶的追隨。此帳戶的追隨中、追隨者數量變為0。由於不會解除追隨者,你的追隨者仍然可以繼續檢視這個帳戶發布給追隨者的貼文。"
+  movedTo: "要遷移到的帳戶:"
 _achievements:
   earnedAt: "獲得日期"
   _types:
@@ -1125,31 +1178,31 @@ _achievements:
       description: "首次追隨了"
     _following10:
       title: "è·Ÿè‘—è·Ÿè‘—"
-      description: "跟隨超過10人了"
+      description: "追隨超過10人了"
     _following50:
       title: "朋友很多"
-      description: "跟隨超過50人了"
+      description: "追隨超過50人了"
     _following100:
       title: "100位朋友"
-      description: "跟隨超過100人了"
+      description: "追隨超過100人了"
     _following300:
       title: "朋友過多"
-      description: "跟隨超過300人了"
+      description: "追隨超過300人了"
     _followers1:
       title: "第一個追隨者"
       description: "第一次被追隨"
     _followers10:
       title: "Follow me!"
-      description: "跟隨者超過10人了"
+      description: "追隨者超過10人了"
     _followers50:
       title: "成群結隊"
-      description: "跟隨者超過50人了"
+      description: "追隨者超過50人了"
     _followers100:
       title: "熱門人物"
-      description: "跟隨者超過100人了"
+      description: "追隨者超過100人了"
     _followers300:
       title: "請排成一排"
-      description: "跟隨者超過300人了"
+      description: "追隨者超過300人了"
     _followers500:
       title: "基地台"
       description: "超過500名追隨者了"
@@ -1171,7 +1224,10 @@ _achievements:
       description: "發現了隱藏的寶藏"
     _client30min:
       title: "休息一下"
-      description: "用戶端啟動已超過30分鐘"
+      description: "客戶端啟動已超過30分鐘"
+    _client60min:
+      title: "Misskey看太多"
+      description: "客戶端啟動已超過60分鐘"
     _noteDeletedWithin1min:
       title: "現在沒有了"
       description: "發文後1分鐘內刪文"
@@ -1260,6 +1316,8 @@ _role:
   iconUrl: "圖示的URL"
   asBadge: "顯示為徽章"
   descriptionOfAsBadge: "開啟的話,角色圖示會顯示在用戶名旁邊。"
+  isExplorable: "公開角色時間軸"
+  descriptionOfIsExplorable: "若開啟則公開角色時間軸。若角色不是公開的,則無法公開時間軸。"
   displayOrder: "顯示順序"
   descriptionOfDisplayOrder: "數字越大,顯示在UI上的越上面。"
   canEditMembersByModerator: "允許編輯審查員的成員"
@@ -1276,6 +1334,7 @@ _role:
     canInvite: "發行實例邀請碼"
     canManageCustomEmojis: "管理自訂表情符號"
     driveCapacity: "雲端硬碟容量"
+    alwaysMarkNsfw: "總是將檔案標記為NSFW"
     pinMax: "置頂貼文的最大數量"
     antennaMax: "可建立的天線數量"
     wordMuteMax: "靜音文字的最大字數"
@@ -1527,32 +1586,6 @@ _time:
   minute: "分鐘"
   hour: "小時"
   day: "æ—¥"
-_tutorial:
-  title: "Misskey使用方法"
-  step1_1: "歡迎!"
-  step1_2: "此為「時間軸」頁面,它會按照時間順序顯示你「追隨」的人發出的「貼文」。"
-  step1_3: "由於你沒有發佈任何貼文,也沒有追隨任何人,所以你的時間軸目前是空的。"
-  step2_1: "在發文或追隨其他人之前先讓我們設定一下個人資料吧。"
-  step2_2: "提供一些關於自己的資訊來讓其他人更有追隨你的意願。"
-  step3_1: "個人資料都設定好了嗎?"
-  step3_2: "接下來,讓我們來試試看發個文,按一下畫面上的鉛筆圖示來開始"
-  step3_3: "輸入完內容後,按視窗右上角的按鈕來發文"
-  step3_4: "不知道該寫什麼內容嗎?試試看「開始使用Misskey了」如何。"
-  step4_1: "貼文發出去了嗎?"
-  step4_2: "如果你的貼文出現在時間軸上,就代表發文成功。"
-  step5_1: "現在試試看追隨其他人來讓你的時間軸變得更生動吧。"
-  step5_2: "你會在{featured}上看到受歡迎的貼文,你也可以從列表中追隨你喜歡的人,或者在{explore}上找到熱門使用者。"
-  step5_3: "想要追隨其他人,只要點擊他們的大頭貼並按「追隨」即可。"
-  step5_4: "如果使用者的名字旁有鎖頭的圖示,代表他們需要手動核准你的追隨請求。"
-  step6_1: "現在你可以在時間軸上看到其他用戶的貼文。"
-  step6_2: "你也可以對別人的貼文作出「反應」,作出簡單的回覆。"
-  step6_3: "在他人的貼文按下\"+\"圖標,即可選擇喜好的表情符號進行回應。"
-  step7_1: "以上為Misskey的基本操作說明,教學在此告一段落。辛苦了。"
-  step7_2: "歡迎到{help}來瞭解更多Misskey相關介紹。"
-  step7_3: "那麼,祝您在Misskey玩的開心~ 🚀"
-  step8_1: "最後,要不要試試看啟用推播通知呢?"
-  step8_2: "透過接收推播通知,即使沒有打開Misskey,您也會知道反應、追隨與提及的情況。"
-  step8_3: "通知的設定可以在之後變更。"
 _2fa:
   alreadyRegistered: "此設備已經被註冊過了"
   registerTOTP: "開始設定驗證應用程式"
diff --git a/package.json b/package.json
index a22bb2873f1c54fdd7b9c45c1773de594d3eadee..5b1d21b815dcdb2f1f78b80dd9988caae1f07bd6 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
 {
 	"name": "misskey",
-	"version": "13.11.3",
+	"version": "13.12.0",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/misskey-dev/misskey.git"
 	},
-	"packageManager": "pnpm@8.1.1",
+	"packageManager": "pnpm@8.3.1",
 	"workspaces": [
 		"packages/frontend",
 		"packages/backend",
@@ -51,19 +51,19 @@
 		"gulp-replace": "1.1.4",
 		"gulp-terser": "2.1.0",
 		"js-yaml": "4.1.0",
-		"typescript": "5.0.3"
+		"typescript": "5.0.4"
 	},
 	"devDependencies": {
 		"@types/gulp": "4.0.10",
 		"@types/gulp-rename": "2.0.1",
-		"@typescript-eslint/eslint-plugin": "5.57.1",
-		"@typescript-eslint/parser": "5.57.1",
+		"@typescript-eslint/eslint-plugin": "5.59.2",
+		"@typescript-eslint/parser": "5.59.2",
 		"cross-env": "7.0.3",
-		"cypress": "12.9.0",
-		"eslint": "8.37.0",
+		"cypress": "12.11.0",
+		"eslint": "8.39.0",
 		"start-server-and-test": "2.0.0"
 	},
 	"optionalDependencies": {
-		"@tensorflow/tfjs-core": "4.2.0"
+		"@tensorflow/tfjs-core": "4.4.0"
 	}
 }
diff --git a/packages/backend/migration/1680702787050-UserMemo.js b/packages/backend/migration/1680702787050-UserMemo.js
new file mode 100644
index 0000000000000000000000000000000000000000..7446bf8da5d758adbc71a94e12f7660cc7680861
--- /dev/null
+++ b/packages/backend/migration/1680702787050-UserMemo.js
@@ -0,0 +1,18 @@
+export class UserMemo1680702787050 {
+    name = 'UserMemo1680702787050'
+
+    async up(queryRunner) {
+        await queryRunner.query(`CREATE TABLE "user_memo" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "targetUserId" character varying(32) NOT NULL, "memo" character varying(2048) NOT NULL, CONSTRAINT "PK_e9aaa58f7d3699a84d79078f4d9" PRIMARY KEY ("id")); COMMENT ON COLUMN "user_memo"."userId" IS 'The ID of author.'; COMMENT ON COLUMN "user_memo"."targetUserId" IS 'The ID of target user.'; COMMENT ON COLUMN "user_memo"."memo" IS 'Memo.'`);
+        await queryRunner.query(`CREATE INDEX "IDX_650b49c5639b5840ee6a2b8f83" ON "user_memo" ("userId") `);
+        await queryRunner.query(`CREATE INDEX "IDX_66ac4a82894297fd09ba61f3d3" ON "user_memo" ("targetUserId") `);
+        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_faef300913c738265638ba3ebc" ON "user_memo" ("userId", "targetUserId") `);
+        await queryRunner.query(`ALTER TABLE "user_memo" ADD CONSTRAINT "FK_650b49c5639b5840ee6a2b8f83e" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE "user_memo" ADD CONSTRAINT "FK_66ac4a82894297fd09ba61f3d35" FOREIGN KEY ("targetUserId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_memo" DROP CONSTRAINT "FK_66ac4a82894297fd09ba61f3d35"`);
+        await queryRunner.query(`ALTER TABLE "user_memo" DROP CONSTRAINT "FK_650b49c5639b5840ee6a2b8f83e"`);
+        await queryRunner.query(`DROP TABLE "user_memo"`);
+    }
+}
diff --git a/packages/backend/migration/1681400427971-serverRules.js b/packages/backend/migration/1681400427971-serverRules.js
new file mode 100644
index 0000000000000000000000000000000000000000..2364e8e1d2a5190bf15b83de3bbe3f2bd6b8d6f8
--- /dev/null
+++ b/packages/backend/migration/1681400427971-serverRules.js
@@ -0,0 +1,11 @@
+export class ServerRules1681400427971 {
+    name = 'ServerRules1681400427971'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "serverRules" character varying(280) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "serverRules"`);
+    }
+}
diff --git a/packages/backend/migration/1681870960239-RoleTLSetting.js b/packages/backend/migration/1681870960239-RoleTLSetting.js
new file mode 100644
index 0000000000000000000000000000000000000000..2280f44eaa10bb7d98cb4e624f5cfa28f4d9c196
--- /dev/null
+++ b/packages/backend/migration/1681870960239-RoleTLSetting.js
@@ -0,0 +1,12 @@
+export class RoleTLSetting1681870960239 {
+	name = 'RoleTLSetting1681870960239'
+
+	async up(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "role" ADD "isExplorable" boolean NOT NULL DEFAULT false`);
+	}
+	
+	async down(queryRunner) {
+		await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "isExplorable"`);
+	}
+
+}
diff --git a/packages/backend/migration/1682190963894-movedAt.js b/packages/backend/migration/1682190963894-movedAt.js
new file mode 100644
index 0000000000000000000000000000000000000000..1f8f030a5c2eee1a86235dccf7539bfb16b291da
--- /dev/null
+++ b/packages/backend/migration/1682190963894-movedAt.js
@@ -0,0 +1,13 @@
+export class MovedAt1682190963894 {
+    name = 'MovedAt1682190963894'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" ADD "movedAt" TIMESTAMP WITH TIME ZONE`);
+        await queryRunner.query(`COMMENT ON COLUMN "user"."movedAt" IS 'When the user moved to another account'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`COMMENT ON COLUMN "user"."movedAt" IS 'When the user moved to another account'`);
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedAt"`);
+    }
+}
diff --git a/packages/backend/migration/1682754135458-preservedUsernames.js b/packages/backend/migration/1682754135458-preservedUsernames.js
new file mode 100644
index 0000000000000000000000000000000000000000..46a0826f43635f325ce95de6c060fe3786a326b7
--- /dev/null
+++ b/packages/backend/migration/1682754135458-preservedUsernames.js
@@ -0,0 +1,11 @@
+export class PreservedUsernames1682754135458 {
+    name = 'PreservedUsernames1682754135458'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "preservedUsernames" character varying(1024) array NOT NULL 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" }'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "preservedUsernames"`);
+    }
+}
diff --git a/packages/backend/migration/1682985520254-channelColor.js b/packages/backend/migration/1682985520254-channelColor.js
new file mode 100644
index 0000000000000000000000000000000000000000..294b7372b2db5702d50a52ba5cd1c1f88c50ca9d
--- /dev/null
+++ b/packages/backend/migration/1682985520254-channelColor.js
@@ -0,0 +1,11 @@
+export class ChannelColor1682985520254 {
+    name = 'ChannelColor1682985520254'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "channel" ADD "color" character varying(16) NOT NULL DEFAULT '#86b300'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "color"`);
+    }
+}
diff --git a/packages/backend/migration/1683328299359-channelArchive.js b/packages/backend/migration/1683328299359-channelArchive.js
new file mode 100644
index 0000000000000000000000000000000000000000..83695ff537f61f85c888cc50e0eb658c8b75e549
--- /dev/null
+++ b/packages/backend/migration/1683328299359-channelArchive.js
@@ -0,0 +1,13 @@
+export class ChannelArchive1683328299359 {
+    name = 'ChannelArchive1683328299359'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "channel" ADD "isArchived" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`CREATE INDEX "IDX_cc7c72974f1b2f385a8921f094" ON "channel" ("isArchived") `);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_cc7c72974f1b2f385a8921f094"`);
+        await queryRunner.query(`ALTER TABLE "channel" DROP COLUMN "isArchived"`);
+    }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 875774bbd5a16b7f4c8829f04c47d769d15423c3..e0ece2bfe5193b948667f531531216f5e2fea28c 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -23,33 +23,44 @@
 	},
 	"optionalDependencies": {
 		"@swc/core-android-arm64": "1.3.11",
-		"@swc/core-darwin-arm64": "1.3.46",
-		"@swc/core-darwin-x64": "1.3.46",
-		"@swc/core-linux-arm-gnueabihf": "1.3.46",
-		"@swc/core-linux-arm64-gnu": "1.3.46",
-		"@swc/core-linux-arm64-musl": "1.3.46",
-		"@swc/core-linux-x64-gnu": "1.3.46",
-		"@swc/core-linux-x64-musl": "1.3.46",
-		"@swc/core-win32-arm64-msvc": "1.3.46",
-		"@swc/core-win32-ia32-msvc": "1.3.46",
-		"@swc/core-win32-x64-msvc": "1.3.46",
-		"@tensorflow/tfjs": "4.2.0",
-		"@tensorflow/tfjs-node": "4.2.0"
+		"@swc/core-darwin-arm64": "1.3.56",
+		"@swc/core-darwin-x64": "1.3.56",
+		"@swc/core-linux-arm-gnueabihf": "1.3.56",
+		"@swc/core-linux-arm64-gnu": "1.3.56",
+		"@swc/core-linux-arm64-musl": "1.3.56",
+		"@swc/core-linux-x64-gnu": "1.3.56",
+		"@swc/core-linux-x64-musl": "1.3.56",
+		"@swc/core-win32-arm64-msvc": "1.3.56",
+		"@swc/core-win32-ia32-msvc": "1.3.56",
+		"@swc/core-win32-x64-msvc": "1.3.56",
+		"@tensorflow/tfjs": "4.4.0",
+		"@tensorflow/tfjs-node": "4.4.0",
+		"slacc-android-arm-eabi": "0.0.7",
+		"slacc-android-arm64": "0.0.7",
+		"slacc-darwin-arm64": "0.0.7",
+		"slacc-darwin-universal": "0.0.7",
+		"slacc-darwin-x64": "0.0.7",
+		"slacc-linux-arm-gnueabihf": "0.0.7",
+		"slacc-linux-arm64-gnu": "0.0.7",
+		"slacc-linux-arm64-musl": "0.0.7",
+		"slacc-linux-x64-gnu": "0.0.7",
+		"slacc-win32-arm64-msvc": "0.0.7",
+		"slacc-win32-x64-msvc": "0.0.7"
 	},
 	"dependencies": {
-		"@aws-sdk/client-s3": "3.306.0",
-		"@aws-sdk/lib-storage": "3.306.0",
-		"@aws-sdk/node-http-handler": "3.306.0",
-		"@bull-board/api": "5.0.0",
-		"@bull-board/fastify": "5.0.0",
-		"@bull-board/ui": "5.0.0",
+		"@aws-sdk/client-s3": "3.321.1",
+		"@aws-sdk/lib-storage": "3.321.1",
+		"@aws-sdk/node-http-handler": "3.321.1",
+		"@bull-board/api": "5.1.2",
+		"@bull-board/fastify": "5.1.2",
+		"@bull-board/ui": "5.1.2",
 		"@discordapp/twemoji": "14.1.2",
 		"@fastify/accepts": "4.1.0",
 		"@fastify/cookie": "8.3.0",
 		"@fastify/cors": "8.2.1",
 		"@fastify/http-proxy": "9.0.0",
-		"@fastify/multipart": "7.5.0",
-		"@fastify/static": "6.10.0",
+		"@fastify/multipart": "7.6.0",
+		"@fastify/static": "6.10.1",
 		"@fastify/view": "7.4.1",
 		"@nestjs/common": "9.4.0",
 		"@nestjs/core": "9.4.0",
@@ -57,7 +68,7 @@
 		"@peertube/http-signature": "1.7.0",
 		"@sinonjs/fake-timers": "10.0.2",
 		"@swc/cli": "0.1.62",
-		"@swc/core": "1.3.46",
+		"@swc/core": "1.3.56",
 		"accepts": "1.3.8",
 		"ajv": "8.12.0",
 		"archiver": "5.3.1",
@@ -73,25 +84,26 @@
 		"cli-highlight": "2.1.11",
 		"color-convert": "2.0.1",
 		"content-disposition": "0.5.4",
-		"date-fns": "2.29.3",
+		"date-fns": "2.30.0",
 		"deep-email-validator": "0.1.21",
 		"escape-regexp": "0.0.1",
-		"fastify": "4.15.0",
+		"fastify": "4.17.0",
 		"feed": "4.2.2",
-		"file-type": "18.2.1",
+		"file-type": "18.3.0",
 		"fluent-ffmpeg": "2.1.2",
 		"form-data": "4.0.0",
 		"got": "12.6.0",
-		"happy-dom": "8.9.0",
+		"happy-dom": "9.10.2",
 		"hpagent": "1.2.0",
-		"ioredis": "4.28.5",
+		"ioredis": "5.3.2",
 		"ip-cidr": "3.1.0",
 		"is-svg": "4.3.2",
 		"js-yaml": "4.1.0",
 		"jsdom": "21.1.1",
 		"json5": "2.2.3",
 		"jsonld": "8.1.1",
-		"jsrsasign": "10.7.0",
+		"meilisearch": "0.32.3",
+		"jsrsasign": "10.8.6",
 		"mfm-js": "0.23.3",
 		"mime-types": "2.1.35",
 		"misskey-js": "workspace:*",
@@ -111,7 +123,7 @@
 		"pug": "3.0.2",
 		"punycode": "2.3.0",
 		"pureimage": "0.3.17",
-		"qrcode": "1.5.1",
+		"qrcode": "1.5.3",
 		"random-seed": "0.3.0",
 		"ratelimiter": "3.4.1",
 		"re2": "1.18.0",
@@ -119,37 +131,38 @@
 		"reflect-metadata": "0.1.13",
 		"rename": "1.0.4",
 		"rndstr": "1.0.0",
-		"rss-parser": "3.12.0",
-		"rxjs": "7.8.0",
+		"rss-parser": "3.13.0",
+		"rxjs": "7.8.1",
 		"s-age": "1.1.2",
 		"sanitize-html": "2.10.0",
 		"seedrandom": "3.0.5",
-		"semver": "7.3.8",
-		"sharp": "0.32.0",
+		"semver": "7.5.0",
+		"sharp": "0.32.1",
 		"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
+		"slacc": "0.0.7",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"summaly": "github:misskey-dev/summaly",
 		"systeminformation": "5.17.12",
 		"tinycolor2": "1.6.0",
 		"tmp": "0.2.1",
-		"tsc-alias": "1.8.5",
+		"tsc-alias": "1.8.6",
 		"tsconfig-paths": "4.2.0",
 		"twemoji-parser": "14.0.0",
-		"typeorm": "0.3.13",
-		"typescript": "5.0.3",
+		"typeorm": "0.3.15",
+		"typescript": "5.0.4",
 		"ulid": "2.3.0",
 		"unzipper": "0.10.11",
 		"uuid": "9.0.0",
 		"vary": "1.1.2",
-		"web-push": "3.5.0",
+		"web-push": "3.6.1",
 		"websocket": "1.0.34",
 		"ws": "8.13.0",
 		"xev": "3.0.2"
 	},
 	"devDependencies": {
 		"@jest/globals": "29.5.0",
-		"@swc/jest": "0.2.24",
+		"@swc/jest": "0.2.26",
 		"@types/accepts": "1.3.5",
 		"@types/archiver": "5.3.2",
 		"@types/bcryptjs": "2.4.2",
@@ -159,14 +172,13 @@
 		"@types/content-disposition": "0.5.5",
 		"@types/escape-regexp": "0.0.1",
 		"@types/fluent-ffmpeg": "2.1.21",
-		"@types/ioredis": "4.28.10",
-		"@types/jest": "29.5.0",
+		"@types/jest": "29.5.1",
 		"@types/js-yaml": "4.0.5",
 		"@types/jsdom": "21.1.1",
 		"@types/jsonld": "1.5.8",
 		"@types/jsrsasign": "10.5.8",
 		"@types/mime-types": "2.1.1",
-		"@types/node": "18.15.11",
+		"@types/node": "18.16.3",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.7",
 		"@types/oauth": "0.9.1",
@@ -180,7 +192,7 @@
 		"@types/rename": "1.0.4",
 		"@types/sanitize-html": "2.9.0",
 		"@types/semver": "7.3.13",
-		"@types/sharp": "0.31.1",
+		"@types/sharp": "0.32.0",
 		"@types/sinonjs__fake-timers": "8.1.2",
 		"@types/tinycolor2": "1.4.3",
 		"@types/tmp": "0.2.3",
@@ -190,11 +202,11 @@
 		"@types/web-push": "3.3.2",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.4",
-		"@typescript-eslint/eslint-plugin": "5.57.1",
-		"@typescript-eslint/parser": "5.57.1",
+		"@typescript-eslint/eslint-plugin": "5.59.2",
+		"@typescript-eslint/parser": "5.59.2",
 		"aws-sdk-client-mock": "^2.1.1",
 		"cross-env": "7.0.3",
-		"eslint": "8.37.0",
+		"eslint": "8.39.0",
 		"eslint-plugin-import": "2.27.5",
 		"execa": "6.1.0",
 		"jest": "29.5.0",
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 174d0d8bebcf2c1885154980e36c185ddea545e1..5fb4e8ef3cf9880a2d078cda438d5b81372016f5 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -1,7 +1,8 @@
 import { setTimeout } from 'node:timers/promises';
 import { Global, Inject, Module } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { DataSource } from 'typeorm';
+import { MeiliSearch } from 'meilisearch';
 import { DI } from './di-symbols.js';
 import { loadConfig } from './config.js';
 import { createPostgresDataSource } from './postgres.js';
@@ -22,10 +23,25 @@ const $db: Provider = {
 	inject: [DI.config],
 };
 
+const $meilisearch: Provider = {
+	provide: DI.meilisearch,
+	useFactory: (config) => {
+		if (config.meilisearch) {
+			return new MeiliSearch({
+				host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
+				apiKey: config.meilisearch.apiKey,
+			});
+		} else {
+			return null;
+		}
+	},
+	inject: [DI.config],
+};
+
 const $redis: Provider = {
 	provide: DI.redis,
 	useFactory: (config) => {
-		return new Redis({
+		return new Redis.Redis({
 			port: config.redis.port,
 			host: config.redis.host,
 			family: config.redis.family == null ? 0 : config.redis.family,
@@ -40,7 +56,7 @@ const $redis: Provider = {
 const $redisForPub: Provider = {
 	provide: DI.redisForPub,
 	useFactory: (config) => {
-		const redis = new Redis({
+		const redis = new Redis.Redis({
 			port: config.redisForPubsub.port,
 			host: config.redisForPubsub.host,
 			family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
@@ -56,7 +72,7 @@ const $redisForPub: Provider = {
 const $redisForSub: Provider = {
 	provide: DI.redisForSub,
 	useFactory: (config) => {
-		const redis = new Redis({
+		const redis = new Redis.Redis({
 			port: config.redisForPubsub.port,
 			host: config.redisForPubsub.host,
 			family: config.redisForPubsub.family == null ? 0 : config.redisForPubsub.family,
@@ -73,8 +89,8 @@ const $redisForSub: Provider = {
 @Global()
 @Module({
 	imports: [RepositoryModule],
-	providers: [$config, $db, $redis, $redisForPub, $redisForSub],
-	exports: [$config, $db, $redis, $redisForPub, $redisForSub, RepositoryModule],
+	providers: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub],
+	exports: [$config, $db, $meilisearch, $redis, $redisForPub, $redisForSub, RepositoryModule],
 })
 export class GlobalModule implements OnApplicationShutdown {
 	constructor(
diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts
index 279a1fe59dc9f5f11e25191a464b54d457f6d9c0..45ded5495c3d4b1bf37ce0326ac0c767b899229d 100644
--- a/packages/backend/src/boot/common.ts
+++ b/packages/backend/src/boot/common.ts
@@ -34,4 +34,6 @@ export async function jobQueue() {
 
 	jobQueue.get(QueueProcessorService).start();
 	jobQueue.get(ChartManagementService).start();
+
+	return jobQueue;
 }
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index e4f7601fa9637970872ad34755e136a67529a4f2..b41fb603bb002964a960718d0dde0dad7b3b37f1 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -4,7 +4,7 @@
 
 import * as fs from 'node:fs';
 import { fileURLToPath } from 'node:url';
-import { dirname } from 'node:path';
+import { dirname, resolve } from 'node:path';
 import * as yaml from 'js-yaml';
 
 /**
@@ -57,13 +57,11 @@ export type Source = {
 		db?: number;
 		prefix?: string;
 	};
-	elasticsearch: {
+	meilisearch?: {
 		host: string;
-		port: number;
+		port: string;
+		apiKey: string;
 		ssl?: boolean;
-		user?: string;
-		pass?: string;
-		index?: string;
 	};
 
 	proxy?: string;
@@ -84,8 +82,10 @@ export type Source = {
 
 	deliverJobConcurrency?: number;
 	inboxJobConcurrency?: number;
+	relashionshipJobConcurrency?: number;
 	deliverJobPerSec?: number;
 	inboxJobPerSec?: number;
+	relashionshipJobPerSec?: number;
 	deliverJobMaxAttempts?: number;
 	inboxJobMaxAttempts?: number;
 
@@ -132,9 +132,11 @@ const dir = `${_dirname}/../../../.config`;
 /**
  * Path of configuration file
  */
-const path = process.env.NODE_ENV === 'test'
-	? `${dir}/test.yml`
-	: `${dir}/default.yml`;
+const path = process.env.MISSKEY_CONFIG_YML
+	? resolve(dir, process.env.MISSKEY_CONFIG_YML)
+	: process.env.NODE_ENV === 'test'
+		? resolve(dir, 'test.yml')
+		: resolve(dir, 'default.yml');
 
 export function loadConfig() {
 	const meta = JSON.parse(fs.readFileSync(`${_dirname}/../../../built/meta.json`, 'utf-8'));
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index 6c7f2142146e8a627aff382eb2584c6ac3b9de5a..ee1a9a3093c679f585b0f256a30df53fdd0696c6 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -56,6 +56,11 @@ export const FILE_TYPE_BROWSERSAFE = [
 	'audio/webm',
 
 	'audio/aac',
+
+	// see https://github.com/misskey-dev/misskey/pull/10686
+	'audio/flac',
+	'audio/wav',
+	// backward compatibility
 	'audio/x-flac',
 	'audio/vnd.wave',
 ];
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index 3f2a19b771c5534e47ede03e1f7dce8c43e961ea..ab11785e28ad3f5e802ed6cddddd9b59bf13775e 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -1,55 +1,90 @@
 import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
+import { IsNull, In, MoreThan, Not } from 'typeorm';
 
 import { bindThis } from '@/decorators.js';
 import { DI } from '@/di-symbols.js';
-import type { LocalUser } from '@/models/entities/User.js';
-import { User } from '@/models/entities/User.js';
-import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
+import type { Config } from '@/config.js';
+import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
+import type { BlockingsRepository, FollowingsRepository, InstancesRepository, Muting, MutingsRepository, UserListJoiningsRepository, UsersRepository } from '@/models/index.js';
+import type { RelationshipJobData, ThinUser } from '@/queue/types.js';
+import type { User } from '@/models/entities/User.js';
 
+import { IdService } from '@/core/IdService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { UserFollowingService } from '@/core/UserFollowingService.js';
+import { QueueService } from '@/core/QueueService.js';
+import { RelayService } from '@/core/RelayService.js';
+import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
 import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { AccountUpdateService } from '@/core/AccountUpdateService.js';
-import { RelayService } from '@/core/RelayService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { ProxyAccountService } from '@/core/ProxyAccountService.js';
+import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
+import { MetaService } from '@/core/MetaService.js';
+import InstanceChart from '@/core/chart/charts/instance.js';
+import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
 
 @Injectable()
 export class AccountMoveService {
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
 		@Inject(DI.followingsRepository)
 		private followingsRepository: FollowingsRepository,
 
+		@Inject(DI.blockingsRepository)
+		private blockingsRepository: BlockingsRepository,
+
+		@Inject(DI.mutingsRepository)
+		private mutingsRepository: MutingsRepository,
+
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
+
+		@Inject(DI.instancesRepository)
+		private instancesRepository: InstancesRepository,
+
 		private userEntityService: UserEntityService,
+		private idService: IdService,
+		private apPersonService: ApPersonService,
 		private apRendererService: ApRendererService,
 		private apDeliverManagerService: ApDeliverManagerService,
 		private globalEventService: GlobalEventService,
-		private userFollowingService: UserFollowingService,
-		private accountUpdateService: AccountUpdateService,
+		private proxyAccountService: ProxyAccountService,
+		private perUserFollowingChart: PerUserFollowingChart,
+		private federatedInstanceService: FederatedInstanceService,
+		private instanceChart: InstanceChart,
+		private metaService: MetaService,
 		private relayService: RelayService,
+		private cacheService: CacheService,
+		private queueService: QueueService,
 	) {
 	}
 
 	/**
-	 * Move a local account to a remote account.
+	 * Move a local account to a new account.
 	 *
 	 * After delivering Move activity, its local followers unfollow the old account and then follow the new one.
 	 */
 	@bindThis
-	public async moveToRemote(src: LocalUser, dst: User): Promise<unknown> {
-		// Make sure that the destination is a remote account.
-		if (this.userEntityService.isLocalUser(dst)) throw new Error('move destiantion is not remote');
-		if (!dst.uri) throw new Error('destination uri is empty');
+	public async moveFromLocal(src: LocalUser, dst: LocalUser | RemoteUser): Promise<unknown> {
+		const srcUri = this.userEntityService.getUserUri(src);
+		const dstUri = this.userEntityService.getUserUri(dst);
 
 		// add movedToUri to indicate that the user has moved
-		const update = {} as Partial<User>;
-		update.alsoKnownAs = src.alsoKnownAs?.concat([dst.uri]) ?? [dst.uri];
-		update.movedToUri = dst.uri;
+		const update = {} as Partial<LocalUser>;
+		update.alsoKnownAs = src.alsoKnownAs?.includes(dstUri) ? src.alsoKnownAs : src.alsoKnownAs?.concat([dstUri]) ?? [dstUri];
+		update.movedToUri = dstUri;
+		update.movedAt = new Date();
 		await this.usersRepository.update(src.id, update);
+		Object.assign(src, update);
+
+		// Update cache
+		this.cacheService.uriPersonCache.set(srcUri, src);
 
 		const srcPerson = await this.apRendererService.renderPerson(src);
 		const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src));
@@ -64,51 +99,249 @@ export class AccountMoveService {
 		const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true });
 		this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
 
-		// follow the new account and unfollow the old one
-		const followings = await this.followingsRepository.find({
-			relations: {
-				follower: true,
-			},
+		// Unfollow after 24 hours
+		const followings = await this.followingsRepository.findBy({
+			followerId: src.id,
+		});
+		this.queueService.createDelayedUnfollowJob(followings.map(following => ({
+			from: { id: src.id },
+			to: { id: following.followeeId },
+		})), process.env.NODE_ENV === 'test' ? 10000 : 1000 * 60 * 60 * 24);
+
+		await this.postMoveProcess(src, dst);
+
+		return iObj;
+	}
+
+	@bindThis
+	public async postMoveProcess(src: User, dst: User): Promise<void> {
+		// Copy blockings and mutings, and update lists
+		try {
+			await Promise.all([
+				this.copyBlocking(src, dst),
+				this.copyMutings(src, dst),
+				this.updateLists(src, dst),
+			]);
+		} catch {
+			/* skip if any error happens */
+		}
+
+		// follow the new account
+		const proxy = await this.proxyAccountService.fetch();
+		const followings = await this.followingsRepository.findBy({
+			followeeId: src.id,
+			followerHost: IsNull(), // follower is local
+			followerId: proxy ? Not(proxy.id) : undefined,
+		});
+		const followJobs = followings.map(following => ({
+			from: { id: following.followerId },
+			to: { id: dst.id },
+		})) as RelationshipJobData[];
+
+		// Decrease following count instead of unfollowing.
+		try {
+			await this.adjustFollowingCounts(followJobs.map(job => job.from.id), src);
+		} catch {
+			/* skip if any error happens */
+		}
+
+		// Should be queued because this can cause a number of follow per one move.
+		this.queueService.createFollowJob(followJobs);
+	}
+
+	@bindThis
+	public async copyBlocking(src: ThinUser, dst: ThinUser): Promise<void> {
+		// Followers shouldn't overlap with blockers, but the destination account, different from the blockee (i.e., old account), may have followed the local user before moving.
+		// So block the destination account here.
+		const srcBlockings = await this.blockingsRepository.findBy({ blockeeId: src.id });
+		const dstBlockings = await this.blockingsRepository.findBy({ blockeeId: dst.id });
+		const blockerIds = dstBlockings.map(blocking => blocking.blockerId);
+		// reblock the destination account
+		const blockJobs: RelationshipJobData[] = [];
+		for (const blocking of srcBlockings) {
+			if (blockerIds.includes(blocking.blockerId)) continue; // skip if already blocked
+			blockJobs.push({ from: { id: blocking.blockerId }, to: { id: dst.id } });
+		}
+		// no need to unblock the old account because it may be still functional
+		this.queueService.createBlockJob(blockJobs);
+	}
+
+	@bindThis
+	public async copyMutings(src: ThinUser, dst: ThinUser): Promise<void> {
+		// Insert new mutings with the same values except mutee
+		const oldMutings = await this.mutingsRepository.findBy([
+			{ muteeId: src.id, expiresAt: IsNull() },
+			{ muteeId: src.id, expiresAt: MoreThan(new Date()) },
+		]);
+		if (oldMutings.length === 0) return;
+
+		// Check if the destination account is already indefinitely muted by the muter
+		const existingMutingsMuterUserIds = await this.mutingsRepository.findBy(
+			{ muteeId: dst.id, expiresAt: IsNull() },
+		).then(mutings => mutings.map(muting => muting.muterId));
+
+		const newMutings: Map<string, { muterId: string; muteeId: string; createdAt: Date; expiresAt: Date | null; }> = new Map();
+
+		// 重複しないようにIDを生成
+		const genId = (): string => {
+			let id: string;
+			do {
+				id = this.idService.genId();
+			} while (newMutings.has(id));
+			return id;
+		};
+		for (const muting of oldMutings) {
+			if (existingMutingsMuterUserIds.includes(muting.muterId)) continue; // skip if already muted indefinitely
+			newMutings.set(genId(), {
+				...muting,
+				createdAt: new Date(),
+				muteeId: dst.id,
+			});
+		}
+
+		const arrayToInsert = Array.from(newMutings.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
+		await this.mutingsRepository.insert(arrayToInsert);
+	}
+
+	/**
+	 * Update lists while moving accounts.
+	 *   - No removal of the old account from the lists
+	 *   - Users number limit is not checked
+	 *
+	 * @param src ThinUser (old account)
+	 * @param dst User (new account)
+	 * @returns Promise<void>
+	 */
+	@bindThis
+	public async updateLists(src: ThinUser, dst: User): Promise<void> {
+		// Return if there is no list to be updated.
+		const oldJoinings = await this.userListJoiningsRepository.find({
 			where: {
-				followeeId: src.id,
-				followerHost: IsNull(), // follower is local
+				userId: src.id,
 			},
 		});
-		for (const following of followings) {
-			if (!following.follower) continue;
-			try {
-				await this.userFollowingService.follow(following.follower, dst);
-				await this.userFollowingService.unfollow(following.follower, src);
-			} catch {
-				/* empty */
+		if (oldJoinings.length === 0) return;
+
+		const existingUserListIds = await this.userListJoiningsRepository.find({
+			where: {
+				userId: dst.id,
+			},
+		}).then(joinings => joinings.map(joining => joining.userListId));
+
+		const newJoinings: Map<string, { createdAt: Date; userId: string; userListId: string; }> = new Map();
+
+		// 重複しないようにIDを生成
+		const genId = (): string => {
+			let id: string;
+			do {
+				id = this.idService.genId();
+			} while (newJoinings.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(), {
+				createdAt: new Date(),
+				userId: dst.id,
+				userListId: joining.userListId,
+			});
+		}
+
+		const arrayToInsert = Array.from(newJoinings.entries()).map(entry => ({ ...entry[1], id: entry[0] }));
+		await this.userListJoiningsRepository.insert(arrayToInsert);
+
+		// Have the proxy account follow the new account in the same way as UserListService.push
+		if (this.userEntityService.isRemoteUser(dst)) {
+			const proxy = await this.proxyAccountService.fetch();
+			if (proxy) {
+				this.queueService.createFollowJob([{ from: { id: proxy.id }, to: { id: dst.id } }]);
 			}
 		}
+	}
 
-		return iObj;
+	@bindThis
+	private async adjustFollowingCounts(localFollowerIds: string[], oldAccount: User): Promise<void> {
+		if (localFollowerIds.length === 0) return;
+
+		// Set the old account's following and followers counts to 0.
+		await this.usersRepository.update({ id: oldAccount.id }, { followersCount: 0, followingCount: 0 });
+
+		// Decrease following counts of local followers by 1.
+		await this.usersRepository.decrement({ id: In(localFollowerIds) }, 'followingCount', 1);
+
+		// Decrease follower counts of local followees by 1.
+		const oldFollowings = await this.followingsRepository.findBy({ followerId: oldAccount.id });
+		if (oldFollowings.length > 0) {
+			await this.usersRepository.decrement({ id: In(oldFollowings.map(following => following.followeeId)) }, 'followersCount', 1);
+		}
+
+		// Update instance stats by decreasing remote followers count by the number of local followers who were following the old account.
+		if (this.userEntityService.isRemoteUser(oldAccount)) {
+			this.federatedInstanceService.fetch(oldAccount.host).then(async i => {
+				this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length);
+				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+					this.instanceChart.updateFollowers(i.host, false);
+				}
+			});
+		}
+
+		// FIXME: expensive?
+		for (const followerId of localFollowerIds) {
+			this.perUserFollowingChart.update({ id: followerId, host: null }, oldAccount, false);
+		}
 	}
 
 	/**
-	 * Create an alias of an old remote account.
+	 * dstユーザーのalsoKnownAsをfetchPersonしていき、本当にmovedToUrlをdstに指定するユーザーが存在するのかを調べる
 	 *
-	 * The user's new profile will be published to the followers.
+	 * @param dst movedToUrlを指定するユーザー
+	 * @param check 
+	 * @param instant checkがtrueであるユーザーが最初に見つかったら即座にreturnするかどうか
+	 * @returns Promise<LocalUser | RemoteUser | null>
 	 */
 	@bindThis
-	public async createAlias(me: LocalUser, updates: Partial<User>): Promise<unknown> {
-		await this.usersRepository.update(me.id, updates);
+	public async validateAlsoKnownAs(
+		dst: LocalUser | RemoteUser,
+		check: (oldUser: LocalUser | RemoteUser | null, newUser: LocalUser | RemoteUser) => boolean | Promise<boolean> = () => true,
+		instant = false,
+	): Promise<LocalUser | RemoteUser | null> {
+		let resultUser: LocalUser | RemoteUser | null = null;
 
-		// Publish meUpdated event
-		const iObj = await this.userEntityService.pack<true, true>(me.id, me, {
-			detail: true,
-			includeSecrets: true,
-		});
-		this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj);
-
-		if (me.isLocked === false) {
-			await this.userFollowingService.acceptAllFollowRequests(me);
+		if (this.userEntityService.isRemoteUser(dst)) {
+			if ((new Date()).getTime() - (dst.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
+				await this.apPersonService.updatePerson(dst.uri);
+			}
+			dst = await this.apPersonService.fetchPerson(dst.uri) ?? dst;
 		}
 
-		this.accountUpdateService.publishToFollowers(me.id);
+		if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) return null;
 
-		return iObj;
+		const dstUri = this.userEntityService.getUserUri(dst);
+
+		for (const srcUri of dst.alsoKnownAs) {
+			try {
+				let src = await this.apPersonService.fetchPerson(srcUri);
+				if (!src) continue; // oldAccountを探してもこのサーバーに存在しない場合はフォロー関係もないということなのでスルー
+
+				if (this.userEntityService.isRemoteUser(dst)) {
+					if ((new Date()).getTime() - (src.lastFetchedAt?.getTime() ?? 0) > 10 * 1000) {
+						await this.apPersonService.updatePerson(srcUri);
+					}
+
+					src = await this.apPersonService.fetchPerson(srcUri) ?? src;
+				}
+
+				if (src.movedToUri === dstUri) {
+					if (await check(resultUser, src)) {
+						resultUser = src;
+					}
+					if (instant && resultUser) return resultUser;
+				}
+			} catch {
+				/* skip if any error happens */
+			}
+		}
+
+		return resultUser;
 	}
 }
diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts
index 1ca38d8bb0556fd1c7e58b67244ecfe27038c71a..9e223f14922b6b38cc682d9faf2f09dc1061e28a 100644
--- a/packages/backend/src/core/AchievementService.ts
+++ b/packages/backend/src/core/AchievementService.ts
@@ -64,6 +64,7 @@ export const ACHIEVEMENT_TYPES = [
 	'iLoveMisskey',
 	'foundTreasure',
 	'client30min',
+	'client60min',
 	'noteDeletedWithin1min',
 	'postedAtLateNight',
 	'postedAt0min0sec',
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 166c78f479ac0778c7b273326b6ff616286cf45d..2d4226a32de374dd34d94f431e8995da4c55d296 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import type { Antenna } from '@/models/entities/Antenna.js';
 import type { Note } from '@/models/entities/Note.js';
 import type { User } from '@/models/entities/User.js';
diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts
index ee179b7f01f6cd2de6f3218dbaa9043b6852b14e..8dd805552b4e64c9839b1187d7b632540aec72a6 100644
--- a/packages/backend/src/core/AppLockService.ts
+++ b/packages/backend/src/core/AppLockService.ts
@@ -1,7 +1,7 @@
 import { promisify } from 'node:util';
 import { Inject, Injectable } from '@nestjs/common';
 import redisLock from 'redis-lock';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index 561face5c35825f3a2dbaf774149bd9b118ba0ef..cf1e81ffc821648f3dd4b9ddd75bb152fb94a43c 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import type { BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfile, UserProfilesRepository, UsersRepository } from '@/models/index.js';
 import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
 import type { LocalUser, User } from '@/models/entities/User.js';
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index 8775536e4a3d53d44855197ca6085d97da8e3029..d3a1b1b024b3bb6e4b99e3a430c668371097bdca 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -50,6 +50,7 @@ import { WebhookService } from './WebhookService.js';
 import { ProxyAccountService } from './ProxyAccountService.js';
 import { UtilityService } from './UtilityService.js';
 import { FileInfoService } from './FileInfoService.js';
+import { SearchService } from './SearchService.js';
 import { ChartLoggerService } from './chart/ChartLoggerService.js';
 import FederationChart from './chart/charts/federation.js';
 import NotesChart from './chart/charts/notes.js';
@@ -171,6 +172,8 @@ const $VideoProcessingService: Provider = { provide: 'VideoProcessingService', u
 const $WebhookService: Provider = { provide: 'WebhookService', useExisting: WebhookService };
 const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService };
 const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService };
+const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService };
+
 const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
 const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
 const $NotesChart: Provider = { provide: 'NotesChart', useExisting: NotesChart };
@@ -295,6 +298,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		WebhookService,
 		UtilityService,
 		FileInfoService,
+		SearchService,
 		ChartLoggerService,
 		FederationChart,
 		NotesChart,
@@ -413,6 +417,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$WebhookService,
 		$UtilityService,
 		$FileInfoService,
+		$SearchService,
 		$ChartLoggerService,
 		$FederationChart,
 		$NotesChart,
@@ -532,6 +537,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		WebhookService,
 		UtilityService,
 		FileInfoService,
+		SearchService,
 		FederationChart,
 		NotesChart,
 		UsersChart,
@@ -649,6 +655,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$WebhookService,
 		$UtilityService,
 		$FileInfoService,
+		$SearchService,
 		$FederationChart,
 		$NotesChart,
 		$UsersChart,
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 185171dee2396781a5415ad4be3b5f166b8651be..93557ce617998ffc0b79bd35588fcb4eadba0b59 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { DataSource, In, IsNull } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import { IdService } from '@/core/IdService.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
@@ -197,6 +197,22 @@ export class CustomEmojiService {
 			emojis: await this.emojiEntityService.packDetailedMany(ids),
 		});
 	}
+	
+	@bindThis
+	public async setLicenseBulk(ids: Emoji['id'][], license: string | null) {
+		await this.emojisRepository.update({
+			id: In(ids),
+		}, {
+			updatedAt: new Date(),
+			license: license,
+		});
+
+		this.localEmojisCache.refresh();
+
+		this.globalEventService.publishBroadcastStream('emojiUpdated', {
+			emojis: await this.emojiEntityService.packDetailedMany(ids),
+		});
+	}
 
 	@bindThis
 	public async delete(id: Emoji['id']) {
diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts
index bd999c67da0ea982714e0481a0fec95f0ee59315..bd535c603270c78bd000704cd1b50f4e035f3f4e 100644
--- a/packages/backend/src/core/DownloadService.ts
+++ b/packages/backend/src/core/DownloadService.ts
@@ -86,9 +86,13 @@ export class DownloadService {
 
 			const contentDisposition = res.headers['content-disposition'];
 			if (contentDisposition != null) {
-				const parsed = parse(contentDisposition);
-				if (parsed.parameters.filename) {
-					filename = parsed.parameters.filename;
+				try {
+					const parsed = parse(contentDisposition);
+					if (parsed.parameters.filename) {
+						filename = parsed.parameters.filename;
+					}
+				} catch (e) {
+					this.logger.warn(`Failed to parse content-disposition: ${contentDisposition}`, { stack: e });
 				}
 			}
 		}).on('downloadProgress', (progress: Got.Progress) => {
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index c6258474ec055654ea571e05d12c3c61ccc15f1f..1483b55469dbe7a350ba4373ea97d6c77afdebfe 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -59,6 +59,8 @@ type AddFileArgs = {
 	uri?: string | null;
 	/** Mark file as sensitive */
 	sensitive?: boolean | null;
+	/** Extension to force */
+	ext?: string | null;
 
 	requestIp?: string | null;
 	requestHeaders?: Record<string, string> | null;
@@ -125,7 +127,7 @@ export class DriveService {
 	/***
 	 * Save file
 	 * @param path Path for original
-	 * @param name Name for original
+	 * @param name Name for original (should be extention corrected)
 	 * @param type Content-Type for original
 	 * @param hash Hash for original
 	 * @param size Size for original
@@ -151,7 +153,7 @@ export class DriveService {
 			}
 
 			// 拡張子からContent-Typeを設定してそうな挙動を示すオブジェクトストレージ (upcloud?) も存在するので、
-			// 許可されているファイル形式でしか拡張子をつけない
+			// 許可されているファイル形式でしかURLに拡張子をつけない
 			if (!FILE_TYPE_BROWSERSAFE.includes(type)) {
 				ext = '';
 			}
@@ -173,7 +175,7 @@ export class DriveService {
 			//#region Uploads
 			this.registerLogger.info(`uploading original: ${key}`);
 			const uploads = [
-				this.upload(key, fs.createReadStream(path), type, ext, name),
+				this.upload(key, fs.createReadStream(path), type, null, name),
 			];
 
 			if (alts.webpublic) {
@@ -189,7 +191,7 @@ export class DriveService {
 				thumbnailUrl = `${ baseUrl }/${ thumbnailKey }`;
 
 				this.registerLogger.info(`uploading thumbnail: ${thumbnailKey}`);
-				uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext));
+				uploads.push(this.upload(thumbnailKey, alts.thumbnail.data, alts.thumbnail.type, alts.thumbnail.ext, `${name}.thumbnail`));
 			}
 
 			await Promise.all(uploads);
@@ -396,8 +398,9 @@ export class DriveService {
 			);
 	}
 
+	// Expire oldest file (without avatar or banner) of remote user
 	@bindThis
-	private async deleteOldFile(user: RemoteUser) {
+	private async expireOldFile(user: RemoteUser, driveCapacity: number) {
 		const q = this.driveFilesRepository.createQueryBuilder('file')
 			.where('file.userId = :userId', { userId: user.id })
 			.andWhere('file.isLink = FALSE');
@@ -410,12 +413,17 @@ export class DriveService {
 			q.andWhere('file.id != :bannerId', { bannerId: user.bannerId });
 		}
 
+		//This selete is hard coded, be careful if change database schema
+		q.addSelect('SUM("file"."size") OVER (ORDER BY "file"."id" DESC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)', 'acc_usage');
 		q.orderBy('file.id', 'ASC');
 
-		const oldFile = await q.getOne();
+		const fileList = await q.getRawMany();
+		const exceedFileIds = fileList.filter((x: any) => x.acc_usage > driveCapacity).map((x: any) => x.file_id);
 
-		if (oldFile) {
-			this.deleteFile(oldFile, true);
+		for (const fileId of exceedFileIds) {
+			const file = await this.driveFilesRepository.findOneBy({ id: fileId });
+			if (file == null) continue;
+			this.deleteFile(file, true);
 		}
 	}
 
@@ -437,10 +445,16 @@ export class DriveService {
 		sensitive = null,
 		requestIp = null,
 		requestHeaders = null,
+		ext = null,
 	}: AddFileArgs): Promise<DriveFile> {
 		let skipNsfwCheck = false;
 		const instance = await this.metaService.fetch();
-		if (user == null) skipNsfwCheck = true;
+		const userRoleNSFW = user && (await this.roleService.getUserPolicies(user.id)).alwaysMarkNsfw;
+		if (user == null) {
+			skipNsfwCheck = true;
+		} else if (userRoleNSFW) {
+			skipNsfwCheck = true;
+		}
 		if (instance.sensitiveMediaDetection === 'none') skipNsfwCheck = true;
 		if (user && instance.sensitiveMediaDetection === 'local' && this.userEntityService.isRemoteUser(user)) skipNsfwCheck = true;
 		if (user && instance.sensitiveMediaDetection === 'remote' && this.userEntityService.isLocalUser(user)) skipNsfwCheck = true;
@@ -468,7 +482,7 @@ export class DriveService {
 			// DriveFile.nameは256文字, validateFileNameは200文字制限であるため、
 			// extを付加してデータベースの文字数制限に当たることはまずない
 			(name && this.driveFileEntityService.validateFileName(name)) ? name : 'untitled',
-			info.type.ext,
+			ext ?? info.type.ext,
 		);
 
 		if (user && !force) {
@@ -489,22 +503,19 @@ export class DriveService {
 		//#region Check drive usage
 		if (user && !isLink) {
 			const usage = await this.driveFileEntityService.calcDriveUsageOf(user);
+			const isLocalUser = this.userEntityService.isLocalUser(user);
 
 			const policies = await this.roleService.getUserPolicies(user.id);
 			const driveCapacity = 1024 * 1024 * policies.driveCapacityMb;
 			this.registerLogger.debug('drive capacity override applied');
 			this.registerLogger.debug(`overrideCap: ${driveCapacity}bytes, usage: ${usage}bytes, u+s: ${usage + info.size}bytes`);
 
-			this.registerLogger.debug(`drive usage is ${usage} (max: ${driveCapacity})`);
-
 			// If usage limit exceeded
-			if (usage + info.size > driveCapacity) {
-				if (this.userEntityService.isLocalUser(user)) {
+			if (driveCapacity < usage + info.size) {
+				if (isLocalUser) {
 					throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
-				} else {
-				// (アバターまたはバナーを含まず)最も古いファイルを削除する
-					this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser);
 				}
+				await this.expireOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser, driveCapacity - info.size);
 			}
 		}
 		//#endregion
@@ -565,6 +576,7 @@ export class DriveService {
 
 		if (info.sensitive && profile!.autoSensitive) file.isSensitive = true;
 		if (info.sensitive && instance.setSensitiveFlagAutomatically) file.isSensitive = true;
+		if (userRoleNSFW) file.isSensitive = true;
 
 		if (url !== null) {
 			file.src = url;
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index 56660ae0d07715117a176cb98728b6656e5dbe84..2049bd4c60b99dca8cdbbe42c7bb2f646bd19d67 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import type { InstancesRepository } from '@/models/index.js';
 import type { Instance } from '@/models/entities/Instance.js';
 import { MemoryKVCache, RedisKVCache } from '@/misc/cache.js';
@@ -23,8 +23,8 @@ export class FederatedInstanceService {
 		private idService: IdService,
 	) {
 		this.federatedInstanceCache = new RedisKVCache<Instance | null>(this.redisClient, 'federatedInstance', {
-			lifetime: 1000 * 60 * 60 * 24, // 24h
-			memoryCacheLifetime: 1000 * 60 * 30, // 30m
+			lifetime: 1000 * 60 * 30, // 30m
+			memoryCacheLifetime: 1000 * 60 * 3, // 3m
 			fetcher: (key) => this.instancesRepository.findOneBy({ host: key }),
 			toRedisConverter: (value) => JSON.stringify(value),
 			fromRedisConverter: (value) => {
@@ -65,15 +65,18 @@ export class FederatedInstanceService {
 	}
 
 	@bindThis
-	public async updateCachePartial(host: string, data: Partial<Instance>): Promise<void> {
-		host = this.utilityService.toPuny(host);
-	
-		const cached = await this.federatedInstanceCache.get(host);
-		if (cached == null) return;
+	public async update(id: Instance['id'], data: Partial<Instance>): Promise<void> {
+		const result = await this.instancesRepository.createQueryBuilder().update()
+			.set(data)
+			.where('id = :id', { id })
+			.returning('*')
+			.execute()
+			.then((response) => {
+				return response.raw[0];
+			});
+
+		const updated = result.raw[0];
 	
-		this.federatedInstanceCache.set(host, {
-			...cached,
-			...data,
-		});
+		this.federatedInstanceCache.set(updated.host, updated);
 	}
 }
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index bbc8b4332e87153dc56a213703bbe48330a743dd..8103d5afe9725aefa9a26f8f625831803f39e715 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js';
 import { LoggerService } from '@/core/LoggerService.js';
 import { HttpRequestService } from '@/core/HttpRequestService.js';
 import { bindThis } from '@/decorators.js';
+import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import type { DOMWindow } from 'jsdom';
 
 type NodeInfo = {
@@ -42,6 +43,7 @@ export class FetchInstanceMetadataService {
 		private appLockService: AppLockService,
 		private httpRequestService: HttpRequestService,
 		private loggerService: LoggerService,
+		private federatedInstanceService: FederatedInstanceService,
 	) {
 		this.logger = this.loggerService.getLogger('metadata', 'cyan');
 	}
@@ -96,7 +98,7 @@ export class FetchInstanceMetadataService {
 			if (favicon) updates.faviconUrl = favicon;
 			if (themeColor) updates.themeColor = themeColor;
 	
-			await this.instancesRepository.update(instance.id, updates);
+			await this.federatedInstanceService.update(instance.id, updates);
 	
 			this.logger.succ(`Successfuly updated metadata of ${instance.host}`);
 		} catch (e) {
diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts
index e39b134b7ee1336eafc8412ffd08526fcdd80108..b6cae5ea757bc041d655c2ba855118d72f611e06 100644
--- a/packages/backend/src/core/FileInfoService.ts
+++ b/packages/backend/src/core/FileInfoService.ts
@@ -5,7 +5,7 @@ import * as stream from 'node:stream';
 import * as util from 'node:util';
 import { Injectable } from '@nestjs/common';
 import { FSWatcher } from 'chokidar';
-import { fileTypeFromFile } from 'file-type';
+import * as fileType from 'file-type';
 import FFmpeg from 'fluent-ffmpeg';
 import isSvg from 'is-svg';
 import probeImageSize from 'probe-image-size';
@@ -301,21 +301,34 @@ export class FileInfoService {
 		return fs.promises.access(path).then(() => true, () => false);
 	}
 
+	@bindThis
+	public fixMime(mime: string | fileType.MimeType): string {
+		// see https://github.com/misskey-dev/misskey/pull/10686
+		if (mime === "audio/x-flac") {
+			return "audio/flac";
+		}
+		if (mime === "audio/vnd.wave") {
+			return "audio/wav";
+		}
+
+		return mime;
+	}
+
 	/**
 	 * Detect MIME Type and extension
 	 */
 	@bindThis
 	public async detectType(path: string): Promise<{
-	mime: string;
-	ext: string | null;
-}> {
+		mime: string;
+		ext: string | null;
+	}> {
 	// Check 0 byte
 		const fileSize = await this.getFileSize(path);
 		if (fileSize === 0) {
 			return TYPE_OCTET_STREAM;
 		}
 
-		const type = await fileTypeFromFile(path);
+		const type = await fileType.fileTypeFromFile(path);
 
 		if (type) {
 		// XMLはSVGかもしれない
@@ -324,7 +337,7 @@ export class FileInfoService {
 			}
 
 			return {
-				mime: type.mime,
+				mime: this.fixMime(type.mime),
 				ext: type.ext,
 			};
 		}
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 2c2687a90ce2d74ff73ac9187643bac9e68dc483..0ed5241148755948241d4698654c7844e5efcf01 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import type { User } from '@/models/entities/User.js';
 import type { Note } from '@/models/entities/Note.js';
 import type { UserList } from '@/models/entities/UserList.js';
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index 1322927c2c646eee33e95d47d5771f191c39dd8b..0b861be8d0475b55a8725ba7cb76d796f7491b5f 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { DataSource } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import { Meta } from '@/models/entities/Meta.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 79629cb2a83cfe9874757234904ccc4338ab30e5..364976e4a79f70d8f49070fc20cf276fa26b8121 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -1,7 +1,7 @@
 import { setImmediate } from 'node:timers/promises';
 import * as mfm from 'mfm-js';
 import { In, DataSource } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import { extractMentions } from '@/misc/extract-mentions.js';
 import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
@@ -46,6 +46,7 @@ import { bindThis } from '@/decorators.js';
 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: UserProfile['userId']; mutedWords: UserProfile['mutedWords']; }[]>(1000 * 60 * 5);
 
@@ -198,6 +199,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private apRendererService: ApRendererService,
 		private roleService: RoleService,
 		private metaService: MetaService,
+		private searchService: SearchService,
 		private notesChart: NotesChart,
 		private perUserNotesChart: PerUserNotesChart,
 		private activeUsersChart: ActiveUsersChart,
@@ -728,17 +730,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 	@bindThis
 	private index(note: Note) {
-		if (note.text == null || this.config.elasticsearch == null) return;
-		/*
-	es!.index({
-		index: this.config.elasticsearch.index ?? 'misskey_note',
-		id: note.id.toString(),
-		body: {
-			text: normalizeForSearch(note.text),
-			userId: note.userId,
-			userHost: note.userHost,
-		},
-	});*/
+		if (note.text == null && note.cw == null) return;
+		
+		this.searchService.indexNote(note);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index 6691c42836d49c2be44df7b90797322fc0dc56fd..a245908c981cddeb371e9f902da5fcea60198fe8 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -1,5 +1,5 @@
 import { setTimeout } from 'node:timers/promises';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
@@ -111,7 +111,7 @@ export class NotificationService implements OnApplicationShutdown {
 		// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
 		setTimeout(2000, 'unread notification', { signal: this.#shutdownController.signal }).then(async () => {
 			const latestReadNotificationId = await this.redisClient.get(`latestReadNotification:${notifieeId}`);
-			if (latestReadNotificationId && (latestReadNotificationId >= await redisIdPromise)) return;
+			if (latestReadNotificationId && (latestReadNotificationId >= (await redisIdPromise)!)) return;
 
 			this.globalEventService.publishMainStream(notifieeId, 'unreadNotification', packed);
 			this.pushNotificationService.pushNotification(notifieeId, 'notification', packed);
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 9b44cf64133a186a0617911997a9f65d0506c5cb..a4c569bdec60d95873c4fcc7de34455f5e3ed5a9 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import push from 'web-push';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import type { Packed } from '@/misc/json-schema';
diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts
index bac85d7a15f6873889f9753bb650801364883c4b..d4905a5f88ac32762ee8e454433dc7d550ba3acf 100644
--- a/packages/backend/src/core/QueueModule.ts
+++ b/packages/backend/src/core/QueueModule.ts
@@ -78,7 +78,7 @@ const $db: Provider = {
 
 const $relationship: Provider = {
 	provide: 'queue:relationship',
-	useFactory: (config: Config) => q(config, 'relationship'),
+	useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64),
 	inject: [DI.config],
 };
 
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index 375ac499117270c0704c8dd3e04ce1ae3b93ed75..b4ffffecc0a65371b057fdc568d97011138ecf06 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -1,15 +1,16 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { v4 as uuid } from 'uuid';
+import Bull from 'bull';
 import type { IActivity } from '@/core/activitypub/type.js';
 import type { DriveFile } from '@/models/entities/DriveFile.js';
 import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
+import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
 import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
 import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
 import type httpSignature from '@peertube/http-signature';
-import Bull from 'bull';
 
 @Injectable()
 export class QueueService {
@@ -152,6 +153,16 @@ export class QueueService {
 		});
 	}
 
+	@bindThis
+	public createExportAntennasJob(user: ThinUser) {
+		return this.dbQueue.add('exportAntennas', {
+			user: { id: user.id },
+		}, {
+			removeOnComplete: true,
+			removeOnFail: true,
+		});
+	}
+
 	@bindThis
 	public createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) {
 		return this.dbQueue.add('importFollowing', {
@@ -235,6 +246,17 @@ export class QueueService {
 		});
 	}
 
+	@bindThis
+	public createImportAntennasJob(user: ThinUser, antenna: Antenna) {
+		return this.dbQueue.add('importAntennas', {
+			user: { id: user.id },
+			antenna,
+		}, {
+			removeOnComplete: true,
+			removeOnFail: true,
+		});
+	}
+
 	@bindThis
 	public createDeleteAccountJob(user: ThinUser, opts: { soft?: boolean; } = {}) {
 		return this.dbQueue.add('deleteAccount', {
@@ -258,6 +280,12 @@ export class QueueService {
 		return this.relationshipQueue.addBulk(jobs);
 	}
 
+	@bindThis
+	public createDelayedUnfollowJob(followings: { from: ThinUser, to: ThinUser, requestId?: string }[], delay: number) {
+		const jobs = followings.map(rel => this.generateRelationshipJobData('unfollow', rel, { delay }));
+		return this.relationshipQueue.addBulk(jobs);
+	}
+
 	@bindThis
 	public createBlockJob(blockings: { from: ThinUser, to: ThinUser, silent?: boolean }[]) {
 		const jobs = blockings.map(rel => this.generateRelationshipJobData('block', rel));
@@ -271,7 +299,7 @@ export class QueueService {
 	}
 
 	@bindThis
-	private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData): {
+	private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobOptions = {}): {
 		name: string,
 		data: RelationshipJobData,
 		opts: Bull.JobOptions,
@@ -287,6 +315,7 @@ export class QueueService {
 			opts: {
 				removeOnComplete: true,
 				removeOnFail: true,
+				...opts,
 			},
 		};
 	}
diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts
index b72dce5180758e76306ee6c3e325e1ef5f82f62f..ff68c242191dce10f799c0a803acf0aab5c2b4d6 100644
--- a/packages/backend/src/core/RemoteUserResolveService.ts
+++ b/packages/backend/src/core/RemoteUserResolveService.ts
@@ -4,7 +4,7 @@ import chalk from 'chalk';
 import { IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { UsersRepository } from '@/models/index.js';
-import type { RemoteUser, User } from '@/models/entities/User.js';
+import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -33,7 +33,7 @@ export class RemoteUserResolveService {
 	}
 
 	@bindThis
-	public async resolveUser(username: string, host: string | null): Promise<User> {
+	public async resolveUser(username: string, host: string | null): Promise<LocalUser | RemoteUser> {
 		const usernameLower = username.toLowerCase();
 	
 		if (host == null) {
@@ -44,7 +44,7 @@ export class RemoteUserResolveService {
 				} else {
 					return u;
 				}
-			});
+			}) as LocalUser;
 		}
 	
 		host = this.utilityService.toPuny(host);
@@ -57,7 +57,7 @@ export class RemoteUserResolveService {
 				} else {
 					return u;
 				}
-			});
+			}) as LocalUser;
 		}
 	
 		const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null;
@@ -109,7 +109,7 @@ export class RemoteUserResolveService {
 				if (u == null) {
 					throw new Error('user not found');
 				} else {
-					return u;
+					return u as LocalUser | RemoteUser;
 				}
 			});
 		}
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 2a4271aa986f7535dcd938c00574bccb770dc232..68087ccc3b34248fa38f626ade439747d5551cb3 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { In } from 'typeorm';
 import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
 import { MemoryKVCache, MemorySingleCache } from '@/misc/cache.js';
@@ -25,6 +25,7 @@ export type RolePolicies = {
 	canSearchNotes: boolean;
 	canHideAds: boolean;
 	driveCapacityMb: number;
+	alwaysMarkNsfw: boolean;
 	pinLimit: number;
 	antennaLimit: number;
 	wordMuteLimit: number;
@@ -45,6 +46,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
 	canSearchNotes: false,
 	canHideAds: false,
 	driveCapacityMb: 100,
+	alwaysMarkNsfw: false,
 	pinLimit: 5,
 	antennaLimit: 5,
 	wordMuteLimit: 200,
@@ -279,6 +281,7 @@ export class RoleService implements OnApplicationShutdown {
 			canSearchNotes: calc('canSearchNotes', vs => vs.some(v => v === true)),
 			canHideAds: calc('canHideAds', vs => vs.some(v => v === true)),
 			driveCapacityMb: calc('driveCapacityMb', vs => Math.max(...vs)),
+			alwaysMarkNsfw: calc('alwaysMarkNsfw', vs => vs.some(v => v === true)),
 			pinLimit: calc('pinLimit', vs => Math.max(...vs)),
 			antennaLimit: calc('antennaLimit', vs => Math.max(...vs)),
 			wordMuteLimit: calc('wordMuteLimit', vs => Math.max(...vs)),
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e68fde088dd8e7f35d7ed01d7a0e988e8754595c
--- /dev/null
+++ b/packages/backend/src/core/SearchService.ts
@@ -0,0 +1,178 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { In } from 'typeorm';
+import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
+import { bindThis } from '@/decorators.js';
+import { Note } from '@/models/entities/Note.js';
+import { User } from '@/models/index.js';
+import type { NotesRepository } from '@/models/index.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
+import { QueryService } from '@/core/QueryService.js';
+import { IdService } from '@/core/IdService.js';
+import type { Index, MeiliSearch } from 'meilisearch';
+
+type K = string;
+type V = string | number | boolean;
+type Q =
+	{ op: '=', k: K, v: V } |
+	{ op: '!=', k: K, v: V } |
+	{ op: '>', k: K, v: number } |
+	{ op: '<', k: K, v: number } |
+	{ op: '>=', k: K, v: number } |
+	{ op: '<=', k: K, v: number } |
+	{ op: 'and', qs: Q[] } |
+	{ op: 'or', qs: Q[] } |
+	{ op: 'not', q: Q };
+
+function compileValue(value: V): string {
+	if (typeof value === 'string') {
+		return `'${value}'`; // TODO: escape
+	} else if (typeof value === 'number') {
+		return value.toString();
+	} else if (typeof value === 'boolean') {
+		return value.toString();
+	}
+	throw new Error('unrecognized value');
+}
+
+function compileQuery(q: Q): string {
+	switch (q.op) {
+		case '=': return `(${q.k} = ${compileValue(q.v)})`;
+		case '!=': return `(${q.k} != ${compileValue(q.v)})`;
+		case '>': return `(${q.k} > ${compileValue(q.v)})`;
+		case '<': return `(${q.k} < ${compileValue(q.v)})`;
+		case '>=': return `(${q.k} >= ${compileValue(q.v)})`;
+		case '<=': return `(${q.k} <= ${compileValue(q.v)})`;
+		case 'and': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' AND ') })`;
+		case 'or': return q.qs.length === 0 ? '' : `(${ q.qs.map(_q => compileQuery(_q)).join(' OR ') })`;
+		case 'not': return `(NOT ${compileQuery(q.q)})`;
+		default: throw new Error('unrecognized query operator');
+	}
+}
+
+@Injectable()
+export class SearchService {
+	private meilisearchNoteIndex: Index | null = null;
+
+	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
+		@Inject(DI.meilisearch)
+		private meilisearch: MeiliSearch | null,
+
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+
+		private queryService: QueryService,
+		private idService: IdService,
+	) {
+		if (meilisearch) {
+			this.meilisearchNoteIndex = meilisearch.index('notes');
+			this.meilisearchNoteIndex.updateSettings({
+				searchableAttributes: [
+					'text',
+					'cw',
+				],
+				sortableAttributes: [
+					'createdAt',
+				],
+				filterableAttributes: [
+					'createdAt',
+					'userId',
+					'userHost',
+					'channelId',
+				],
+				typoTolerance: {
+					enabled: false,
+				},
+				pagination: {
+					maxTotalHits: 10000,
+				},
+			});
+		}
+	}
+
+	@bindThis
+	public async indexNote(note: Note): Promise<void> {
+		if (note.text == null && note.cw == null) return;
+		if (!['home', 'public'].includes(note.visibility)) return;
+
+		if (this.meilisearch) {
+			this.meilisearchNoteIndex!.addDocuments([{
+				id: note.id,
+				createdAt: note.createdAt.getTime(),
+				userId: note.userId,
+				userHost: note.userHost,
+				channelId: note.channelId,
+				cw: note.cw,
+				text: note.text,
+			}], {
+				primaryKey: 'id',
+			});
+		}
+	}
+
+	@bindThis
+	public async searchNote(q: string, me: User | null, opts: {
+		userId?: Note['userId'] | null;
+		channelId?: Note['channelId'] | null;
+		host?: string | null;
+	}, pagination: {
+		untilId?: Note['id'];
+		sinceId?: Note['id'];
+		limit?: number;
+	}): Promise<Note[]> {
+		if (this.meilisearch) {
+			const filter: Q = {
+				op: 'and',
+				qs: [],
+			};
+			if (pagination.untilId) filter.qs.push({ op: '<', k: 'createdAt', v: this.idService.parse(pagination.untilId).date.getTime() });
+			if (pagination.sinceId) filter.qs.push({ op: '>', k: 'createdAt', v: this.idService.parse(pagination.sinceId).date.getTime() });
+			if (opts.userId) filter.qs.push({ op: '=', k: 'userId', v: opts.userId });
+			if (opts.channelId) filter.qs.push({ op: '=', k: 'channelId', v: opts.channelId });
+			if (opts.host) {
+				if (opts.host === '.') {
+					// TODO: Meilisearchが2023/05/07現在値がNULLかどうかのクエリが書けない
+				} else {
+					filter.qs.push({ op: '=', k: 'userHost', v: opts.host });
+				}
+			}
+			const res = await this.meilisearchNoteIndex!.search(q, {
+				sort: ['createdAt:desc'],
+				matchingStrategy: 'all',
+				attributesToRetrieve: ['id', 'createdAt'],
+				filter: compileQuery(filter),
+				limit: pagination.limit,
+			});
+			if (res.hits.length === 0) return [];
+			const notes = await this.notesRepository.findBy({
+				id: In(res.hits.map(x => x.id)),
+			});
+			return notes.sort((a, b) => a.id > b.id ? -1 : 1);
+		} else {
+			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), pagination.sinceId, pagination.untilId);
+
+			if (opts.userId) {
+				query.andWhere('note.userId = :userId', { userId: opts.userId });
+			} else if (opts.channelId) {
+				query.andWhere('note.channelId = :channelId', { channelId: opts.channelId });
+			}
+
+			query
+				.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(q) }%` })
+				.innerJoinAndSelect('note.user', 'user')
+				.leftJoinAndSelect('note.reply', 'reply')
+				.leftJoinAndSelect('note.renote', 'renote')
+				.leftJoinAndSelect('reply.user', 'replyUser')
+				.leftJoinAndSelect('renote.user', 'renoteUser');
+
+			this.queryService.generateVisibilityQuery(query, me);
+			if (me) this.queryService.generateMutedUserQuery(query, me);
+			if (me) this.queryService.generateBlockedUserQuery(query, me);
+
+			return await query.take(pagination.limit).getMany();
+		}
+	}
+}
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index d7bc05b8bdc5784a6d64c1bce0d8b6d8c1de7ce0..29eb65fda4e9a338e1d09f31f35fb77eed651539 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -13,8 +13,9 @@ import { UsedUsername } from '@/models/entities/UsedUsername.js';
 import generateUserToken from '@/misc/generate-native-user-token.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.js';
-import UsersChart from './chart/charts/users.js';
-import { UtilityService } from './UtilityService.js';
+import UsersChart from '@/core/chart/charts/users.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { MetaService } from '@/core/MetaService.js';
 
 @Injectable()
 export class SignupService {
@@ -34,6 +35,7 @@ export class SignupService {
 		private utilityService: UtilityService,
 		private userEntityService: UserEntityService,
 		private idService: IdService,
+		private metaService: MetaService,
 		private usersChart: UsersChart,
 	) {
 	}
@@ -44,6 +46,7 @@ export class SignupService {
 		password?: string | null;
 		passwordHash?: UserProfile['password'] | null;
 		host?: string | null;
+		ignorePreservedUsernames?: boolean;
 	}) {
 		const { username, password, passwordHash, host } = opts;
 		let hash = passwordHash;
@@ -76,7 +79,17 @@ export class SignupService {
 		if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) {
 			throw new Error('USED_USERNAME');
 		}
-	
+
+		const isTheFirstUser = (await this.usersRepository.countBy({ host: IsNull() })) === 0;
+
+		if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
+			const instance = await this.metaService.fetch(true);
+			const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
+			if (isPreserved) {
+				throw new Error('USED_USERNAME');
+			}
+		}
+
 		const keyPair = await new Promise<string[]>((res, rej) =>
 			generateKeyPair('rsa', {
 				modulusLength: 4096,
@@ -112,9 +125,7 @@ export class SignupService {
 				usernameLower: username.toLowerCase(),
 				host: this.utilityService.toPunyNullable(host),
 				token: secret,
-				isRoot: (await this.usersRepository.countBy({
-					host: IsNull(),
-				})) === 0,
+				isRoot: isTheFirstUser,
 			}));
 	
 			await transactionalEntityManager.save(new UserKeypair({
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index a8eded673351fe1fe85550278799a33ec36f58b4..7d90bc2c080eb11228150c395e37419e90557baa 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable, OnModuleInit, forwardRef } from '@nestjs/common';
 import { ModuleRef } from '@nestjs/core';
-import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
+import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { QueueService } from '@/core/QueueService.js';
 import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@@ -22,6 +22,8 @@ import { MetaService } from '@/core/MetaService.js';
 import { CacheService } from '@/core/CacheService.js';
 import type { Config } from '@/config.js';
 import Logger from '../logger.js';
+import { IsNull } from 'typeorm';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 
 const logger = new Logger('following/create');
 
@@ -73,6 +75,7 @@ export class UserFollowingService implements OnModuleInit {
 		private federatedInstanceService: FederatedInstanceService,
 		private webhookService: WebhookService,
 		private apRendererService: ApRendererService,
+		private accountMoveService: AccountMoveService,
 		private perUserFollowingChart: PerUserFollowingChart,
 		private instanceChart: InstanceChart,
 	) {
@@ -87,7 +90,7 @@ export class UserFollowingService implements OnModuleInit {
 		const [follower, followee] = await Promise.all([
 			this.usersRepository.findOneByOrFail({ id: _follower.id }),
 			this.usersRepository.findOneByOrFail({ id: _followee.id }),
-		]);
+		]) as [LocalUser | RemoteUser, LocalUser | RemoteUser];
 
 		// check blocking
 		const [blocking, blocked] = await Promise.all([
@@ -137,6 +140,20 @@ export class UserFollowingService implements OnModuleInit {
 				if (followed) autoAccept = true;
 			}
 
+			// Automatically accept if the follower is an account who has moved and the locked followee had accepted the old account.
+			if (followee.isLocked && !autoAccept) {
+				autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
+					follower,
+					(oldSrc, newSrc) => this.followingsRepository.exist({
+						where: {
+							followeeId: followee.id,
+							followerId: newSrc.id,
+						},
+					}),
+					true,
+				));
+			}
+
 			if (!autoAccept) {
 				await this.createFollowRequest(follower, followee, requestId);
 				return;
@@ -210,32 +227,40 @@ export class UserFollowingService implements OnModuleInit {
 
 		this.globalEventService.publishInternalEvent('follow', { followerId: follower.id, followeeId: followee.id });
 
-		//#region Increment counts
-		await Promise.all([
-			this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
-			this.usersRepository.increment({ id: followee.id }, 'followersCount', 1),
+		const [followeeUser, followerUser] = await Promise.all([
+			this.usersRepository.findOneByOrFail({ id: followee.id }),
+			this.usersRepository.findOneByOrFail({ id: follower.id }),
 		]);
-		//#endregion
 
-		//#region Update instance stats
-		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			this.federatedInstanceService.fetch(follower.host).then(async i => {
-				this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-					this.instanceChart.updateFollowing(i.host, true);
-				}
-			});
-		} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			this.federatedInstanceService.fetch(followee.host).then(async i => {
-				this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-					this.instanceChart.updateFollowers(i.host, true);
-				}
-			});
-		}
-		//#endregion
+		// Neither followee nor follower has moved.
+		if (!followeeUser.movedToUri && !followerUser.movedToUri) {
+			//#region Increment counts
+			await Promise.all([
+				this.usersRepository.increment({ id: follower.id }, 'followingCount', 1),
+				this.usersRepository.increment({ id: followee.id }, 'followersCount', 1),
+			]);
+			//#endregion
+
+			//#region Update instance stats
+			if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
+				this.federatedInstanceService.fetch(follower.host).then(async i => {
+					this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
+					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+						this.instanceChart.updateFollowing(i.host, true);
+					}
+				});
+			} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
+				this.federatedInstanceService.fetch(followee.host).then(async i => {
+					this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
+					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+						this.instanceChart.updateFollowers(i.host, true);
+					}
+				});
+			}
+			//#endregion
 
-		this.perUserFollowingChart.update(follower, followee, true);
+			this.perUserFollowingChart.update(follower, followee, true);
+		}
 
 		// Publish follow event
 		if (this.userEntityService.isLocalUser(follower) && !silent) {
@@ -283,12 +308,18 @@ export class UserFollowingService implements OnModuleInit {
 		},
 		silent = false,
 	): Promise<void> {
-		const following = await this.followingsRepository.findOneBy({
-			followerId: follower.id,
-			followeeId: followee.id,
+		const following = await this.followingsRepository.findOne({
+			relations: {
+				follower: true,
+				followee: true,
+			},
+			where: {
+				followerId: follower.id,
+				followeeId: followee.id,
+			}
 		});
 
-		if (following == null) {
+		if (following === null || !following.follower || !following.followee) {
 			logger.warn('フォロー解除がリクエストされましたがフォローしていませんでした');
 			return;
 		}
@@ -297,7 +328,7 @@ export class UserFollowingService implements OnModuleInit {
 
 		this.cacheService.userFollowingsCache.refresh(follower.id);
 
-		this.decrementFollowing(follower, followee);
+		this.decrementFollowing(following.follower, following.followee);
 
 		// Publish unfollow event
 		if (!silent && this.userEntityService.isLocalUser(follower)) {
@@ -316,50 +347,87 @@ export class UserFollowingService implements OnModuleInit {
 		}
 
 		if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
+			const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser), follower));
 			this.queueService.deliver(follower, content, followee.inbox, false);
 		}
 
 		if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) {
 			// local user has null host
-			const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee));
+			const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower as PartialRemoteUser, followee as PartialLocalUser), followee));
 			this.queueService.deliver(followee, content, follower.inbox, false);
 		}
 	}
 
 	@bindThis
 	private async decrementFollowing(
-		follower: { id: User['id']; host: User['host']; },
-		followee: { id: User['id']; host: User['host']; },
+		follower: User,
+		followee: User,
 	): Promise<void> {
 		this.globalEventService.publishInternalEvent('unfollow', { followerId: follower.id, followeeId: followee.id });
 
-		//#region Decrement following / followers counts
-		await Promise.all([
-			this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1),
-			this.usersRepository.decrement({ id: followee.id }, 'followersCount', 1),
-		]);
-		//#endregion
+		// Neither followee nor follower has moved.
+		if (!follower.movedToUri && !followee.movedToUri) {
+			//#region Decrement following / followers counts
+			await Promise.all([
+				this.usersRepository.decrement({ id: follower.id }, 'followingCount', 1),
+				this.usersRepository.decrement({ id: followee.id }, 'followersCount', 1),
+			]);
+			//#endregion
+
+			//#region Update instance stats
+			if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
+				this.federatedInstanceService.fetch(follower.host).then(async i => {
+					this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
+					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+						this.instanceChart.updateFollowing(i.host, false);
+					}
+				});
+			} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
+				this.federatedInstanceService.fetch(followee.host).then(async i => {
+					this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
+					if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
+						this.instanceChart.updateFollowers(i.host, false);
+					}
+				});
+			}
+			//#endregion
 
-		//#region Update instance stats
-		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			this.federatedInstanceService.fetch(follower.host).then(async i => {
-				this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-					this.instanceChart.updateFollowing(i.host, false);
-				}
-			});
-		} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			this.federatedInstanceService.fetch(followee.host).then(async i => {
-				this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
-				if ((await this.metaService.fetch()).enableChartsForFederatedInstances) {
-					this.instanceChart.updateFollowers(i.host, false);
-				}
-			});
-		}
-		//#endregion
+			this.perUserFollowingChart.update(follower, followee, false);
+		} else {
+			// Adjust following/followers counts
+			for (const user of [follower, followee]) {
+				if (user.movedToUri) continue; // No need to update if the user has already moved.
+
+				const nonMovedFollowees = await this.followingsRepository.count({
+					relations: {
+						followee: true,
+					},
+					where: {
+						followerId: user.id,
+						followee: {
+							movedToUri: IsNull(),
+						}
+					}
+				});
+				const nonMovedFollowers = await this.followingsRepository.count({
+					relations: {
+						follower: true,
+					},
+					where: {
+						followeeId: user.id,
+						follower: {
+							movedToUri: IsNull(),
+						}
+					}
+				});
+				await this.usersRepository.update(
+					{ id: user.id },
+					{ followingCount: nonMovedFollowees, followersCount: nonMovedFollowers },
+				);
+			}
 
-		this.perUserFollowingChart.update(follower, followee, false);
+			// TODO: adjust charts
+		}
 	}
 
 	@bindThis
@@ -415,7 +483,7 @@ export class UserFollowingService implements OnModuleInit {
 		}
 
 		if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, requestId ?? `${this.config.url}/follows/${followRequest.id}`));
+			const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower as PartialLocalUser, followee as PartialRemoteUser, requestId ?? `${this.config.url}/follows/${followRequest.id}`));
 			this.queueService.deliver(follower, content, followee.inbox, false);
 		}
 	}
@@ -430,7 +498,7 @@ export class UserFollowingService implements OnModuleInit {
 		},
 	): Promise<void> {
 		if (this.userEntityService.isRemoteUser(followee)) {
-			const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
+			const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower as PartialLocalUser | PartialRemoteUser, followee as PartialRemoteUser), follower));
 
 			if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので
 				this.queueService.deliver(follower, content, followee.inbox, false);
@@ -475,7 +543,7 @@ export class UserFollowingService implements OnModuleInit {
 		await this.insertFollowingDoc(followee, follower);
 
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee));
+			const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as PartialLocalUser, request.requestId!), followee));
 			this.queueService.deliver(followee, content, follower.inbox, false);
 		}
 
@@ -562,15 +630,22 @@ export class UserFollowingService implements OnModuleInit {
 	 */
 	@bindThis
 	private async removeFollow(followee: Both, follower: Both): Promise<void> {
-		const following = await this.followingsRepository.findOneBy({
-			followeeId: followee.id,
-			followerId: follower.id,
+		const following = await this.followingsRepository.findOne({
+			relations: {
+				followee: true,
+				follower: true,
+			},
+			where: {
+				followeeId: followee.id,
+				followerId: follower.id,
+			}
 		});
 
-		if (!following) return;
+		if (!following || !following.followee || !following.follower) return;
 
 		await this.followingsRepository.delete(following.id);
-		this.decrementFollowing(follower, followee);
+
+		this.decrementFollowing(following.follower, following.followee);
 	}
 
 	/**
diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts
index 22a9fb2b8ef3185f352815cdb6109a69cd7c371c..72c35c529c2752cff574090eea9ce54ea9972d3a 100644
--- a/packages/backend/src/core/UserKeypairService.ts
+++ b/packages/backend/src/core/UserKeypairService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import type { User } from '@/models/entities/User.js';
 import type { UserKeypairsRepository } from '@/models/index.js';
 import { RedisKVCache } from '@/misc/cache.js';
diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts
index d00bb89c7635293742355b0da6d8ea119b8b5923..b197d335d89c756fb3637a8be3619439aef8dd45 100644
--- a/packages/backend/src/core/UserSuspendService.ts
+++ b/packages/backend/src/core/UserSuspendService.ts
@@ -35,7 +35,7 @@ export class UserSuspendService {
 	
 		if (this.userEntityService.isLocalUser(user)) {
 			// 知り得る全SharedInboxにDelete配信
-			const content = this.apRendererService.addContext(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user));
+			const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user));
 	
 			const queue: string[] = [];
 	
@@ -65,7 +65,7 @@ export class UserSuspendService {
 	
 		if (this.userEntityService.isLocalUser(user)) {
 			// 知り得る全SharedInboxにUndo Delete配信
-			const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user), user));
+			const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(this.userEntityService.genLocalUserUri(user.id), user), user));
 	
 			const queue: string[] = [];
 	
diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts
index 69df2d0c1b67f6ddf65c9e487cc856887a4e73b8..3ee79906431f5d9aa9b05ba560d6ed435de25664 100644
--- a/packages/backend/src/core/WebfingerService.ts
+++ b/packages/backend/src/core/WebfingerService.ts
@@ -43,7 +43,8 @@ export class WebfingerService {
 		const m = query.match(/^([^@]+)@(.*)/);
 		if (m) {
 			const hostname = m[2];
-			return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` });
+			const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true';
+			return `http${useHttp ? '' : 's'}://${hostname}/.well-known/webfinger?${urlQuery({ resource: `acct:${query}` })}`;
 		}
 
 		throw new Error(`Invalid query (${query})`);
diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts
index 926115613b85f93e1a299858f01e8267933e9407..57baade77707a60defcbdcb23a14e5326801761a 100644
--- a/packages/backend/src/core/WebhookService.ts
+++ b/packages/backend/src/core/WebhookService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import type { WebhooksRepository } from '@/models/index.js';
 import type { Webhook } from '@/models/entities/Webhook.js';
 import { DI } from '@/di-symbols.js';
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index 4b032be89adb44fca347e1a2a6215050cc84fb67..2b404ebecaeb64d7ff3a8323a4200436f77b8739 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -8,7 +8,7 @@ import type { UserPublickey } from '@/models/entities/UserPublickey.js';
 import { CacheService } from '@/core/CacheService.js';
 import type { Note } from '@/models/entities/Note.js';
 import { bindThis } from '@/decorators.js';
-import { RemoteUser, User } from '@/models/entities/User.js';
+import { LocalUser, RemoteUser } from '@/models/entities/User.js';
 import { getApId } from './type.js';
 import { ApPersonService } from './models/ApPersonService.js';
 import type { IObject } from './type.js';
@@ -101,7 +101,7 @@ export class ApDbResolverService {
 	 * AP Person => Misskey User in DB
 	 */
 	@bindThis
-	public async getUserFromApId(value: string | IObject): Promise<User | null> {
+	public async getUserFromApId(value: string | IObject): Promise<LocalUser | RemoteUser | null> {
 		const parsed = this.parseUri(value);
 
 		if (parsed.local) {
@@ -109,11 +109,11 @@ export class ApDbResolverService {
 
 			return await this.cacheService.userByIdCache.fetchMaybe(parsed.id, () => this.usersRepository.findOneBy({
 				id: parsed.id,
-			}).then(x => x ?? undefined)) ?? null;
+			}).then(x => x ?? undefined)) as LocalUser | undefined ?? null;
 		} else {
 			return await this.cacheService.uriPersonCache.fetch(parsed.uri, () => this.usersRepository.findOneBy({
 				uri: parsed.uri,
-			}));
+			})) as RemoteUser | null;
 		}
 	}
 
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index 3fca0bb1fd5784972d8f09d528bb7158f7072381..efef777fb07e03563c0c17a754680b6f8a38afb5 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import { In, IsNull } from 'typeorm';
+import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { UserFollowingService } from '@/core/UserFollowingService.js';
@@ -13,13 +13,15 @@ import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
 import { AppLockService } from '@/core/AppLockService.js';
 import type Logger from '@/logger.js';
 import { MetaService } from '@/core/MetaService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 import { IdService } from '@/core/IdService.js';
 import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { CacheService } from '@/core/CacheService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { QueueService } from '@/core/QueueService.js';
-import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
+import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository, } from '@/models/index.js';
 import { bindThis } from '@/decorators.js';
 import type { RemoteUser } from '@/models/entities/User.js';
 import { getApHrefNullable, getApId, getApIds, getApType, getOneApHrefNullable, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
@@ -76,6 +78,8 @@ export class ApInboxService {
 		private apNoteService: ApNoteService,
 		private apPersonService: ApPersonService,
 		private apQuestionService: ApQuestionService,
+		private accountMoveService: AccountMoveService,
+		private cacheService: CacheService,
 		private queueService: QueueService,
 	) {
 		this.logger = this.apLoggerService.logger;
@@ -140,7 +144,7 @@ export class ApInboxService {
 		} else if (isFlag(activity)) {
 			await this.flag(actor, activity);
 		} else if (isMove(activity)) {
-			//await this.move(actor, activity);
+			await this.move(actor, activity);
 		} else {
 			this.logger.warn(`unrecognized activity type: ${activity.type}`);
 		}
@@ -158,6 +162,7 @@ export class ApInboxService {
 			return 'skip: フォローしようとしているユーザーはローカルユーザーではありません';
 		}
 
+		// don't queue because the sender may attempt again when timeout
 		await this.userFollowingService.follow(actor, followee, activity.id);
 		return 'ok';
 	}
@@ -596,6 +601,7 @@ export class ApInboxService {
 			throw e;
 		});
 
+		// don't queue because the sender may attempt again when timeout
 		if (isFollow(object)) return await this.undoFollow(actor, object);
 		if (isBlock(object)) return await this.undoBlock(actor, object);
 		if (isLike(object)) return await this.undoLike(actor, object);
@@ -736,53 +742,7 @@ export class ApInboxService {
 		// fetch the new and old accounts
 		const targetUri = getApHrefNullable(activity.target);
 		if (!targetUri) return 'skip: invalid activity target';
-		let new_acc = await this.apPersonService.resolvePerson(targetUri);
-		let old_acc = await this.apPersonService.resolvePerson(actor.uri);
-
-		// update them if they're remote
-		if (new_acc.uri) await this.apPersonService.updatePerson(new_acc.uri);
-		if (old_acc.uri) await this.apPersonService.updatePerson(old_acc.uri);
-
-		// retrieve updated users
-		new_acc = await this.apPersonService.resolvePerson(targetUri);
-		old_acc = await this.apPersonService.resolvePerson(actor.uri);
-
-		// check if alsoKnownAs of the new account is valid
-		let isValidMove = true;
-		if (old_acc.uri) {
-			if (!new_acc.alsoKnownAs?.includes(old_acc.uri)) {
-				isValidMove = false;
-			}
-		} else if (!new_acc.alsoKnownAs?.includes(old_acc.id)) {
-			isValidMove = false;
-		}
-		if (!isValidMove) {
-			return 'skip: accounts invalid';
-		}
-
-		// add target uri to movedToUri in order to indicate that the user has moved
-		await this.usersRepository.update(old_acc.id, { movedToUri: targetUri });
-
-		// follow the new account and unfollow the old one
-		const followings = await this.followingsRepository.find({
-			relations: {
-				follower: true,
-			},
-			where: {
-				followeeId: old_acc.id,
-				followerHost: IsNull(), // follower is local
-			},
-		});
-		for (const following of followings) {
-			if (!following.follower) continue;
-			try {
-				await this.userFollowingService.follow(following.follower, new_acc);
-				await this.userFollowingService.unfollow(following.follower, old_acc);
-			} catch {
-				/* empty */
-			}
-		}
 
-		return 'ok';
+		return await this.apPersonService.updatePerson(actor.uri) ?? 'skip: nothing to do';
 	}
 }
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 0b22aa9bcfb26555968fef13f7fbd6faaaf1a89c..60e19bfca52f46eb194f15775f71067180077e83 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid';
 import * as mfm from 'mfm-js';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
-import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
+import type { PartialLocalUser, LocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
 import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js';
 import type { Blocking } from '@/models/entities/Blocking.js';
 import type { Relay } from '@/models/entities/Relay.js';
@@ -66,7 +66,7 @@ export class ApRendererService {
 	public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept {
 		return {
 			type: 'Accept',
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			object,
 		};
 	}
@@ -75,7 +75,7 @@ export class ApRendererService {
 	public renderAdd(user: LocalUser, target: any, object: any): IAdd {
 		return {
 			type: 'Add',
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			target,
 			object,
 		};
@@ -83,7 +83,7 @@ export class ApRendererService {
 
 	@bindThis
 	public renderAnnounce(object: any, note: Note): IAnnounce {
-		const attributedTo = `${this.config.url}/users/${note.userId}`;
+		const attributedTo = this.userEntityService.genLocalUserUri(note.userId);
 
 		let to: string[] = [];
 		let cc: string[] = [];
@@ -103,7 +103,7 @@ export class ApRendererService {
 
 		return {
 			id: `${this.config.url}/notes/${note.id}/activity`,
-			actor: `${this.config.url}/users/${note.userId}`,
+			actor: this.userEntityService.genLocalUserUri(note.userId),
 			type: 'Announce',
 			published: note.createdAt.toISOString(),
 			to,
@@ -126,7 +126,7 @@ export class ApRendererService {
 		return {
 			type: 'Block',
 			id: `${this.config.url}/blocks/${block.id}`,
-			actor: `${this.config.url}/users/${block.blockerId}`,
+			actor: this.userEntityService.genLocalUserUri(block.blockerId),
 			object: block.blockee.uri,
 		};
 	}
@@ -135,7 +135,7 @@ export class ApRendererService {
 	public renderCreate(object: IObject, note: Note): ICreate {
 		const activity = {
 			id: `${this.config.url}/notes/${note.id}/activity`,
-			actor: `${this.config.url}/users/${note.userId}`,
+			actor: this.userEntityService.genLocalUserUri(note.userId),
 			type: 'Create',
 			published: note.createdAt.toISOString(),
 			object,
@@ -151,7 +151,7 @@ export class ApRendererService {
 	public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete {
 		return {
 			type: 'Delete',
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			object,
 			published: new Date().toISOString(),
 		};
@@ -188,7 +188,7 @@ export class ApRendererService {
 	public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag {
 		return {
 			type: 'Flag',
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			content,
 			object,
 		};
@@ -199,7 +199,7 @@ export class ApRendererService {
 		return {
 			id: `${this.config.url}/activities/follow-relay/${relay.id}`,
 			type: 'Follow',
-			actor: `${this.config.url}/users/${relayActor.id}`,
+			actor: this.userEntityService.genLocalUserUri(relayActor.id),
 			object: 'https://www.w3.org/ns/activitystreams#Public',
 		};
 	}
@@ -210,21 +210,21 @@ export class ApRendererService {
 	 */
 	@bindThis
 	public async renderFollowUser(id: User['id']) {
-		const user = await this.usersRepository.findOneByOrFail({ id: id });
-		return this.userEntityService.isLocalUser(user) ? `${this.config.url}/users/${user.id}` : user.uri;
+		const user = await this.usersRepository.findOneByOrFail({ id: id }) as PartialLocalUser | PartialRemoteUser;
+		return this.userEntityService.getUserUri(user);
 	}
 
 	@bindThis
 	public renderFollow(
-		follower: { id: User['id']; host: User['host']; uri: User['host'] },
-		followee: { id: User['id']; host: User['host']; uri: User['host'] },
+		follower: PartialLocalUser | PartialRemoteUser,
+		followee: PartialLocalUser | PartialRemoteUser,
 		requestId?: string,
 	): IFollow {
 		return {
 			id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
 			type: 'Follow',
-			actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri!,
-			object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri!,
+			actor: this.userEntityService.getUserUri(follower)!,
+			object: this.userEntityService.getUserUri(followee)!,
 		};
 	}
 
@@ -252,7 +252,7 @@ export class ApRendererService {
 		return {
 			id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
 			type: 'Key',
-			owner: `${this.config.url}/users/${user.id}`,
+			owner: this.userEntityService.genLocalUserUri(user.id),
 			publicKeyPem: createPublicKey(key.publicKey).export({
 				type: 'spki',
 				format: 'pem',
@@ -284,21 +284,21 @@ export class ApRendererService {
 	}
 
 	@bindThis
-	public renderMention(mention: User): IApMention {
+	public renderMention(mention: PartialLocalUser | PartialRemoteUser): IApMention {
 		return {
 			type: 'Mention',
-			href: this.userEntityService.isRemoteUser(mention) ? mention.uri! : `${this.config.url}/users/${(mention as LocalUser).id}`,
+			href: this.userEntityService.getUserUri(mention)!,
 			name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`,
 		};
 	}
 
 	@bindThis
 	public renderMove(
-		src: { id: User['id']; host: User['host']; uri: User['host'] },
-		dst: { id: User['id']; host: User['host']; uri: User['host'] },
+		src: PartialLocalUser | PartialRemoteUser,
+		dst: PartialLocalUser | PartialRemoteUser,
 	): IMove {
-		const actor = this.userEntityService.isLocalUser(src) ? `${this.config.url}/users/${src.id}` : src.uri!;
-		const target = this.userEntityService.isLocalUser(dst) ? `${this.config.url}/users/${dst.id}` : dst.uri!;
+		const actor = this.userEntityService.getUserUri(src)!;
+		const target = this.userEntityService.getUserUri(dst)!;
 		return {
 			id: `${this.config.url}/moves/${src.id}/${dst.id}`,
 			actor,
@@ -351,7 +351,7 @@ export class ApRendererService {
 			}
 		}
 
-		const attributedTo = `${this.config.url}/users/${note.userId}`;
+		const attributedTo = this.userEntityService.genLocalUserUri(note.userId);
 
 		const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri);
 
@@ -376,7 +376,7 @@ export class ApRendererService {
 		}) : [];
 
 		const hashtagTags = (note.tags ?? []).map(tag => this.renderHashtag(tag));
-		const mentionTags = mentionedUsers.map(u => this.renderMention(u));
+		const mentionTags = mentionedUsers.map(u => this.renderMention(u as LocalUser | RemoteUser));
 
 		const files = await getPromisedFiles(note.fileIds);
 
@@ -450,7 +450,7 @@ export class ApRendererService {
 
 	@bindThis
 	public async renderPerson(user: LocalUser) {
-		const id = `${this.config.url}/users/${user.id}`;
+		const id = this.userEntityService.genLocalUserUri(user.id);
 		const isSystem = !!user.username.match(/\./);
 
 		const [avatar, banner, profile] = await Promise.all([
@@ -538,7 +538,7 @@ export class ApRendererService {
 		return {
 			type: 'Question',
 			id: `${this.config.url}/questions/${note.id}`,
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			content: note.text ?? '',
 			[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
 				name: text,
@@ -555,7 +555,7 @@ export class ApRendererService {
 	public renderReject(object: any, user: { id: User['id'] }): IReject {
 		return {
 			type: 'Reject',
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			object,
 		};
 	}
@@ -564,7 +564,7 @@ export class ApRendererService {
 	public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove {
 		return {
 			type: 'Remove',
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			target,
 			object,
 		};
@@ -585,7 +585,7 @@ export class ApRendererService {
 		return {
 			type: 'Undo',
 			...(id ? { id } : {}),
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			object,
 			published: new Date().toISOString(),
 		};
@@ -595,7 +595,7 @@ export class ApRendererService {
 	public renderUpdate(object: any, user: { id: User['id'] }): IUpdate {
 		return {
 			id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			type: 'Update',
 			to: ['https://www.w3.org/ns/activitystreams#Public'],
 			object,
@@ -607,14 +607,14 @@ export class ApRendererService {
 	public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: RemoteUser): ICreate {
 		return {
 			id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`,
-			actor: `${this.config.url}/users/${user.id}`,
+			actor: this.userEntityService.genLocalUserUri(user.id),
 			type: 'Create',
 			to: [pollOwner.uri],
 			published: new Date().toISOString(),
 			object: {
 				id: `${this.config.url}/users/${user.id}#votes/${vote.id}`,
 				type: 'Note',
-				attributedTo: `${this.config.url}/users/${user.id}`,
+				attributedTo: this.userEntityService.genLocalUserUri(user.id),
 				to: [pollOwner.uri],
 				inReplyTo: note.uri,
 				name: poll.choices[vote.choice],
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index df7bb464056d4f1f20d61f66258c637b68509c88..d3e0345c9c275814217860e2364ffba016bfa3af 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import type { LocalUser } from '@/models/entities/User.js';
+import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
 import { InstanceActorService } from '@/core/InstanceActorService.js';
 import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -151,7 +151,7 @@ export class Resolver {
 				return Promise.all(
 					[parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })),
 				)
-					.then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, url)));
+					.then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower as LocalUser | RemoteUser, followee as LocalUser | RemoteUser, url)));
 			default:
 				throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
 		}
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index 3b671af1277b4eafdaddc52733026f9f12ac4447..0043907c212f5c299db14e13f6e5109e0dec6d7e 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -12,6 +12,7 @@ import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { ApResolverService } from '../ApResolverService.js';
 import { ApLoggerService } from '../ApLoggerService.js';
+import { checkHttps } from '@/misc/check-https.js';
 
 @Injectable()
 export class ApImageService {
@@ -48,8 +49,8 @@ export class ApImageService {
 			throw new Error('invalid image: url not privided');
 		}
 
-		if (!image.url.startsWith('https://')) {
-			throw new Error('invalid image: unexpected shcema of url: ' + image.url);
+		if (!checkHttps(image.url)) {
+			throw new Error('invalid image: unexpected schema of url: ' + image.url);
 		}
 
 		this.logger.info(`Creating the Image: ${image.url}`);
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 5ca5f6e843c737465978600ccee52e5c8c1ade2d..87a9db405f9d0fcc56deb27b915e3b8062bdae8a 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -32,6 +32,7 @@ import { ApQuestionService } from './ApQuestionService.js';
 import { ApImageService } from './ApImageService.js';
 import type { Resolver } from '../ApResolverService.js';
 import type { IObject, IPost } from '../type.js';
+import { checkHttps } from '@/misc/check-https.js';
 
 @Injectable()
 export class ApNoteService {
@@ -71,7 +72,7 @@ export class ApNoteService {
 	}
 
 	@bindThis
-	public validateNote(object: any, uri: string) {
+	public validateNote(object: IObject, uri: string) {
 		const expectHost = this.utilityService.extractDbHost(uri);
 	
 		if (object == null) {
@@ -85,9 +86,10 @@ export class ApNoteService {
 		if (object.id && this.utilityService.extractDbHost(object.id) !== expectHost) {
 			return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.id)}`);
 		}
-	
-		if (object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo)) !== expectHost) {
-			return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${this.utilityService.extractDbHost(object.attributedTo)}`);
+
+		const actualHost = object.attributedTo && this.utilityService.extractDbHost(getOneApId(object.attributedTo));
+		if (object.attributedTo && actualHost !== expectHost) {
+			return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
 		}
 	
 		return null;
@@ -129,13 +131,13 @@ export class ApNoteService {
 	
 		this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
 
-		if (note.id && !note.id.startsWith('https://')) {
+		if (note.id && !checkHttps(note.id)) {
 			throw new Error('unexpected shcema of note.id: ' + note.id);
 		}
 
 		const url = getOneApHrefNullable(note.url);
 
-		if (url && !url.startsWith('https://')) {
+		if (url && !checkHttps(url)) {
 			throw new Error('unexpected shcema of note url: ' + url);
 		}
 	
@@ -148,7 +150,7 @@ export class ApNoteService {
 		if (actor.isSuspended) {
 			throw new Error('actor has been suspended');
 		}
-	
+		
 		const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
 		let visibility = noteAudience.visibility;
 		const visibleUsers = noteAudience.visibleUsers;
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 21797cfcb7ae2b46bcb683bc8aacc32fbed11aa2..eea1d1b8482825e01de66924013f1de9a3c99559 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -3,9 +3,9 @@ import promiseLimit from 'promise-limit';
 import { DataSource } from 'typeorm';
 import { ModuleRef } from '@nestjs/core';
 import { DI } from '@/di-symbols.js';
-import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
+import type { BlockingsRepository, MutingsRepository, FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
-import type { RemoteUser } from '@/models/entities/User.js';
+import type { LocalUser, RemoteUser } from '@/models/entities/User.js';
 import { User } from '@/models/entities/User.js';
 import { truncate } from '@/misc/truncate.js';
 import type { CacheService } from '@/core/CacheService.js';
@@ -42,6 +42,8 @@ import type { ApLoggerService } from '../ApLoggerService.js';
 // eslint-disable-next-line @typescript-eslint/consistent-type-imports
 import type { ApImageService } from './ApImageService.js';
 import type { IActor, IObject } from '../type.js';
+import type { AccountMoveService } from '@/core/AccountMoveService.js';
+import { checkHttps } from '@/misc/check-https.js';
 
 const nameLength = 128;
 const summaryLength = 2048;
@@ -66,6 +68,7 @@ export class ApPersonService implements OnModuleInit {
 	private usersChart: UsersChart;
 	private instanceChart: InstanceChart;
 	private apLoggerService: ApLoggerService;
+	private accountMoveService: AccountMoveService;
 	private logger: Logger;
 
 	constructor(
@@ -131,9 +134,16 @@ export class ApPersonService implements OnModuleInit {
 		this.usersChart = this.moduleRef.get('UsersChart');
 		this.instanceChart = this.moduleRef.get('InstanceChart');
 		this.apLoggerService = this.moduleRef.get('ApLoggerService');
+		this.accountMoveService = this.moduleRef.get('AccountMoveService');
 		this.logger = this.apLoggerService.logger;
 	}
 
+	private punyHost(url: string): string {
+		const urlObj = new URL(url);
+		const host = `${this.utilityService.toPuny(urlObj.hostname)}${urlObj.port.length > 0 ? ':' + urlObj.port : ''}`;
+		return host;
+	}
+
 	/**
 	 * Validate and convert to actor object
 	 * @param x Fetched object
@@ -141,7 +151,7 @@ export class ApPersonService implements OnModuleInit {
 	 */
 	@bindThis
 	private validateActor(x: IObject, uri: string): IActor {
-		const expectHost = this.utilityService.toPuny(new URL(uri).hostname);
+		const expectHost = this.punyHost(uri);
 
 		if (x == null) {
 			throw new Error('invalid Actor: object is null');
@@ -182,7 +192,7 @@ export class ApPersonService implements OnModuleInit {
 			x.summary = truncate(x.summary, summaryLength);
 		}
 
-		const idHost = this.utilityService.toPuny(new URL(x.id!).hostname);
+		const idHost = this.punyHost(x.id);
 		if (idHost !== expectHost) {
 			throw new Error('invalid Actor: id has different host');
 		}
@@ -192,7 +202,7 @@ export class ApPersonService implements OnModuleInit {
 				throw new Error('invalid Actor: publicKey.id is not a string');
 			}
 
-			const publicKeyIdHost = this.utilityService.toPuny(new URL(x.publicKey.id).hostname);
+			const publicKeyIdHost = this.punyHost(x.publicKey.id);
 			if (publicKeyIdHost !== expectHost) {
 				throw new Error('invalid Actor: publicKey.id has different host');
 			}
@@ -202,27 +212,27 @@ export class ApPersonService implements OnModuleInit {
 	}
 
 	/**
-	 * Personをフェッチします。
+	 * uriからUser(Person)をフェッチします。
 	 *
-	 * Misskeyに対象のPersonが登録されていればそれを返します。
+	 * Misskeyに対象のPersonが登録されていればそれを返し、登録がなければnullを返します。
 	 */
 	@bindThis
-	public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
+	public async fetchPerson(uri: string): Promise<LocalUser | RemoteUser | null> {
 		if (typeof uri !== 'string') throw new Error('uri is not string');
 
-		const cached = this.cacheService.uriPersonCache.get(uri);
+		const cached = this.cacheService.uriPersonCache.get(uri) as LocalUser | RemoteUser | null;
 		if (cached) return cached;
 
 		// URIがこのサーバーを指しているならデータベースからフェッチ
-		if (uri.startsWith(this.config.url + '/')) {
+		if (uri.startsWith(`${this.config.url}/`)) {
 			const id = uri.split('/').pop();
-			const u = await this.usersRepository.findOneBy({ id });
+			const u = await this.usersRepository.findOneBy({ id }) as LocalUser;
 			if (u) this.cacheService.uriPersonCache.set(uri, u);
 			return u;
 		}
 
 		//#region このサーバーに既に登録されていたらそれを返す
-		const exist = await this.usersRepository.findOneBy({ uri });
+		const exist = await this.usersRepository.findOneBy({ uri }) as LocalUser | RemoteUser;
 
 		if (exist) {
 			this.cacheService.uriPersonCache.set(uri, exist);
@@ -237,7 +247,7 @@ export class ApPersonService implements OnModuleInit {
 	 * Personを作成します。
 	 */
 	@bindThis
-	public async createPerson(uri: string, resolver?: Resolver): Promise<User> {
+	public async createPerson(uri: string, resolver?: Resolver): Promise<RemoteUser> {
 		if (typeof uri !== 'string') throw new Error('uri is not string');
 
 		if (uri.startsWith(this.config.url)) {
@@ -252,7 +262,7 @@ export class ApPersonService implements OnModuleInit {
 
 		this.logger.info(`Creating the Person: ${person.id}`);
 
-		const host = this.utilityService.toPuny(new URL(object.id).hostname);
+		const host = this.punyHost(object.id);
 
 		const { fields } = this.analyzeAttachments(person.attachment ?? []);
 
@@ -264,8 +274,8 @@ export class ApPersonService implements OnModuleInit {
 
 		const url = getOneApHrefNullable(person.url);
 
-		if (url && !url.startsWith('https://')) {
-			throw new Error('unexpected shcema of person url: ' + url);
+		if (url && !checkHttps(url)) {
+			throw new Error('unexpected schema of person url: ' + url);
 		}
 
 		// Create user
@@ -282,6 +292,7 @@ export class ApPersonService implements OnModuleInit {
 					name: truncate(person.name, nameLength),
 					isLocked: !!person.manuallyApprovesFollowers,
 					movedToUri: person.movedTo,
+					movedAt: person.movedTo ? new Date() : null,
 					alsoKnownAs: person.alsoKnownAs,
 					isExplorable: !!person.discoverable,
 					username: person.preferredUsername,
@@ -404,23 +415,26 @@ export class ApPersonService implements OnModuleInit {
 	/**
 	 * Personの情報を更新します。
 	 * Misskeyに対象のPersonが登録されていなければ無視します。
+	 * もしアカウントの移行が確認された場合、アカウント移行処理を行います。
+	 * 
 	 * @param uri URI of Person
 	 * @param resolver Resolver
 	 * @param hint Hint of Person object (この値が正当なPersonの場合、Remote resolveをせずに更新に利用します)
+	 * @param movePreventUris ここに指定されたURIがPersonのmovedToに指定されていたり10回より多く回っている場合これ以上アカウント移行を行わない(無限ループ防止)
 	 */
 	@bindThis
-	public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject): Promise<void> {
+	public async updatePerson(uri: string, resolver?: Resolver | null, hint?: IObject, movePreventUris: string[] = []): Promise<string | void> {
 		if (typeof uri !== 'string') throw new Error('uri is not string');
 
 		// URIがこのサーバーを指しているならスキップ
-		if (uri.startsWith(this.config.url + '/')) {
+		if (uri.startsWith(`${this.config.url}/`)) {
 			return;
 		}
 
 		//#region このサーバーに既に登録されているか
-		const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser;
+		const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser | null;
 
-		if (exist == null) {
+		if (exist === null) {
 			return;
 		}
 		//#endregion
@@ -459,8 +473,8 @@ export class ApPersonService implements OnModuleInit {
 
 		const url = getOneApHrefNullable(person.url);
 
-		if (url && !url.startsWith('https://')) {
-			throw new Error('unexpected shcema of person url: ' + url);
+		if (url && !checkHttps(url)) {
+			throw new Error('unexpected schema of person url: ' + url);
 		}
 
 		const updates = {
@@ -478,7 +492,16 @@ export class ApPersonService implements OnModuleInit {
 			movedToUri: person.movedTo ?? null,
 			alsoKnownAs: person.alsoKnownAs ?? null,
 			isExplorable: !!person.discoverable,
-		} as Partial<User>;
+		} as Partial<RemoteUser> & Pick<RemoteUser, 'isBot' | 'isCat' | 'isLocked' | 'movedToUri' | 'alsoKnownAs' | 'isExplorable'>;
+
+		const moving =
+			// 移行先がない→ある
+			(!exist.movedToUri && updates.movedToUri) ||
+			// 移行先がある→別のもの
+			(exist.movedToUri !== updates.movedToUri && exist.movedToUri && updates.movedToUri);
+			// 移行先がある→ない、ない→ないは無視
+
+		if (moving) updates.movedAt = new Date();
 
 		if (avatar) {
 			updates.avatarId = avatar.id;
@@ -523,6 +546,31 @@ export class ApPersonService implements OnModuleInit {
 		});
 
 		await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err));
+
+		const updated = { ...exist, ...updates };
+
+		this.cacheService.uriPersonCache.set(uri, updated);
+
+		// 移行処理を行う
+		if (updated.movedAt && (
+			// 初めて移行する場合はmovedAtがnullなので移行処理を許可
+			exist.movedAt == null ||
+			// 以前のmovingから14日以上経過した場合のみ移行処理を許可
+			// (Mastodonのクールダウン期間は30日だが若干緩めに設定しておく)
+			exist.movedAt.getTime() + 1000 * 60 * 60 * 24 * 14 < updated.movedAt.getTime()
+		)) {
+			this.logger.info(`Start to process Move of @${updated.username}@${updated.host} (${uri})`);
+			return this.processRemoteMove(updated, movePreventUris)
+				.then(result => {
+					this.logger.info(`Processing Move Finished [${result}] @${updated.username}@${updated.host} (${uri})`);
+					return result;
+				})
+				.catch(e => {
+					this.logger.info(`Processing Move Failed @${updated.username}@${updated.host} (${uri})`, { stack: e });
+				});
+		}
+
+		return 'skip';
 	}
 
 	/**
@@ -532,7 +580,7 @@ export class ApPersonService implements OnModuleInit {
 	 * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
 	 */
 	@bindThis
-	public async resolvePerson(uri: string, resolver?: Resolver): Promise<User> {
+	public async resolvePerson(uri: string, resolver?: Resolver): Promise<LocalUser | RemoteUser> {
 		if (typeof uri !== 'string') throw new Error('uri is not string');
 
 		//#region このサーバーに既に登録されていたらそれを返す
@@ -607,4 +655,53 @@ export class ApPersonService implements OnModuleInit {
 			}
 		});
 	}
+
+	/**
+	 * リモート由来のアカウント移行処理を行います
+	 * @param src 移行元アカウント(リモートかつupdatePerson後である必要がある、というかこれ自体がupdatePersonで呼ばれる前提)
+	 * @param movePreventUris ここに列挙されたURIにsrc.movedToUriが含まれる場合、移行処理はしない(無限ループ防止)
+	 */
+	@bindThis
+	private async processRemoteMove(src: RemoteUser, movePreventUris: string[] = []): Promise<string> {
+		if (!src.movedToUri) return 'skip: no movedToUri';
+		if (src.uri === src.movedToUri) return 'skip: movedTo itself (src)'; // ???
+		if (movePreventUris.length > 10) return 'skip: too many moves';
+
+		// まずサーバー内で検索して様子見
+		let dst = await this.fetchPerson(src.movedToUri);
+
+		if (dst && this.userEntityService.isLocalUser(dst)) {
+			// targetがローカルユーザーだった場合データベースから引っ張ってくる
+			dst = await this.usersRepository.findOneByOrFail({ uri: src.movedToUri }) as LocalUser;
+		} else if (dst) {
+			if (movePreventUris.includes(src.movedToUri)) return 'skip: circular move';
+
+			// targetを見つけたことがあるならtargetをupdatePersonする
+			await this.updatePerson(src.movedToUri, undefined, undefined, [...movePreventUris, src.uri]);
+			dst = await this.fetchPerson(src.movedToUri) ?? dst;
+		} else {
+			if (src.movedToUri.startsWith(`${this.config.url}/`)) {
+				// ローカルユーザーっぽいのにfetchPersonで見つからないということはmovedToUriが間違っている
+				return 'failed: movedTo is local but not found';
+			}
+
+			// targetが知らない人だったらresolvePerson
+			// (uriが存在しなかったり応答がなかったりする場合resolvePersonはthrow Errorする)
+			dst = await this.resolvePerson(src.movedToUri);
+		}
+ 
+		if (dst.movedToUri === dst.uri) return 'skip: movedTo itself (dst)'; // ???
+		if (src.movedToUri !== dst.uri) return 'skip: missmatch uri'; // ???
+		if (dst.movedToUri === src.uri) return 'skip: dst.movedToUri === src.uri';
+		if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
+			return 'skip: dst.alsoKnownAs is empty';
+		}
+		if (!dst.alsoKnownAs?.includes(src.uri)) {
+			return 'skip: alsoKnownAs does not include from.uri';
+		}
+
+		await this.accountMoveService.postMoveProcess(src, dst);
+
+		return 'ok';
+	}
 }
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 987002606f418d41680f71ef35f2fe7fc0b5063a..15ffd4486136b0759ce4495e141f8be099930bad 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -74,6 +74,8 @@ export class ChannelEntityService {
 			userId: channel.userId,
 			bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
 			pinnedNoteIds: channel.pinnedNoteIds,
+			color: channel.color,
+			isArchived: channel.isArchived,
 			usersCount: channel.usersCount,
 			notesCount: channel.notesCount,
 
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 26debd6adc05df355e7391fbcd8f768f13cc25ab..32269a4101418cc1664e76a4f8880cfbc8cd0175 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -335,6 +335,7 @@ export class NoteEntityService implements OnModuleInit {
 			channel: channel ? {
 				id: channel.id,
 				name: channel.name,
+				color: channel.color,
 			} : undefined,
 			mentions: note.mentions.length > 0 ? note.mentions : undefined,
 			uri: note.uri ?? undefined,
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 0dc63d969f8a89230d2914c1c7a48a5668f5d88d..d76b863957393aa999c8fa60f91d8392d364bb58 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { ModuleRef } from '@nestjs/core';
 import { In } from 'typeorm';
 import { DI } from '@/di-symbols.js';
-import type { AccessTokensRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js';
+import type { AccessTokensRepository, FollowRequestsRepository, NoteReactionsRepository, NotesRepository, User, UsersRepository } from '@/models/index.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import type { Notification } from '@/models/entities/Notification.js';
 import type { Note } from '@/models/entities/Note.js';
@@ -35,6 +35,9 @@ export class NotificationEntityService implements OnModuleInit {
 		@Inject(DI.noteReactionsRepository)
 		private noteReactionsRepository: NoteReactionsRepository,
 
+		@Inject(DI.followRequestsRepository)
+		private followRequestsRepository: FollowRequestsRepository,
+
 		@Inject(DI.accessTokensRepository)
 		private accessTokensRepository: AccessTokensRepository,
 
@@ -131,6 +134,15 @@ export class NotificationEntityService implements OnModuleInit {
 		});
 		const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
 
+		// 既に解決されたフォローリクエストの通知を除外
+		const followRequestNotifications = validNotifications.filter(x => x.type === 'receiveFollowRequest');
+		if (followRequestNotifications.length > 0) {
+			const reqs = await this.followRequestsRepository.find({
+				where: { followerId: In(followRequestNotifications.map(x => x.notifierId!)) },
+			});
+			validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
+		}
+
 		return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, {
 			packedNotes,
 			packedUsers,
diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts
index e111a10b777e66be91619ddc25f79a27a2c2ca75..54818782dd7dedfd7b289555cd63902a00af1ef3 100644
--- a/packages/backend/src/core/entities/RoleEntityService.ts
+++ b/packages/backend/src/core/entities/RoleEntityService.ts
@@ -59,6 +59,7 @@ export class RoleEntityService {
 			isPublic: role.isPublic,
 			isAdministrator: role.isAdministrator,
 			isModerator: role.isModerator,
+			isExplorable: role.isExplorable,
 			asBadge: role.asBadge,
 			canEditMembersByModerator: role.canEditMembersByModerator,
 			displayOrder: role.displayOrder,
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index e02f7535d4307f3a6f9779ff9388742595948a3f..a7f62c05f79af1a708348ff6c051c9b7b3c32bef 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { In, Not } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import Ajv from 'ajv';
 import { ModuleRef } from '@nestjs/core';
 import { DI } from '@/di-symbols.js';
@@ -9,10 +9,9 @@ import type { Packed } from '@/misc/json-schema.js';
 import type { Promiseable } from '@/misc/prelude/await-all.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
-import type { Instance } from '@/models/entities/Instance.js';
-import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
+import type { LocalUser, PartialLocalUser, PartialRemoteUser, RemoteUser, User } from '@/models/entities/User.js';
 import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
-import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository } from '@/models/index.js';
+import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, PagesRepository, UserProfile, RenoteMutingsRepository, UserMemoRepository } from '@/models/index.js';
 import { bindThis } from '@/decorators.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
@@ -35,13 +34,13 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
 const ajv = new Ajv();
 
 function isLocalUser(user: User): user is LocalUser;
-function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
+function isLocalUser<T extends { host: User['host'] }>(user: T): user is (T & { host: null; });
 function isLocalUser(user: User | { host: User['host'] }): boolean {
 	return user.host == null;
 }
 
 function isRemoteUser(user: User): user is RemoteUser;
-function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
+function isRemoteUser<T extends { host: User['host'] }>(user: T): user is (T & { host: string; });
 function isRemoteUser(user: User | { host: User['host'] }): boolean {
 	return !isLocalUser(user);
 }
@@ -113,6 +112,9 @@ export class UserEntityService implements OnModuleInit {
 
 		@Inject(DI.pagesRepository)
 		private pagesRepository: PagesRepository,
+		
+		@Inject(DI.userMemosRepository)
+		private userMemosRepository: UserMemoRepository,
 
 		//private noteEntityService: NoteEntityService,
 		//private driveFileEntityService: DriveFileEntityService,
@@ -277,9 +279,20 @@ export class UserEntityService implements OnModuleInit {
 		return `${this.config.url}/identicon/${user.username.toLowerCase()}@${user.host ?? this.config.host}`;
 	}
 
+	@bindThis
+	public getUserUri(user: LocalUser | PartialLocalUser | RemoteUser | PartialRemoteUser): string {
+		return this.isRemoteUser(user)
+			? user.uri : this.genLocalUserUri(user.id);
+	}
+
+	@bindThis
+	public genLocalUserUri(userId: string): string {
+		return `${this.config.url}/users/${userId}`;
+	}
+
 	public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
 		src: User['id'] | User,
-		me?: { id: User['id'] } | null | undefined,
+		me?: { id: User['id']; } | null | undefined,
 		options?: {
 			detail?: D,
 			includeSecrets?: boolean,
@@ -293,26 +306,9 @@ 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 User) : false;
 
 		const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
 		const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin')
@@ -366,8 +362,11 @@ export class UserEntityService implements OnModuleInit {
 			...(opts.detail ? {
 				url: profile!.url,
 				uri: user.uri,
-				movedToUri: user.movedToUri ? await this.apPersonService.resolvePerson(user.movedToUri) : null,
-				alsoKnownAs: user.alsoKnownAs,
+				movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null,
+				alsoKnownAs: user.alsoKnownAs
+					? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null)))
+						.then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[])
+					: null,
 				createdAt: user.createdAt.toISOString(),
 				updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
 				lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
@@ -409,6 +408,11 @@ export class UserEntityService implements OnModuleInit {
 					isAdministrator: role.isAdministrator,
 					displayOrder: role.displayOrder,
 				}))),
+				memo: meId == null ? null : await this.userMemosRepository.findOneBy({
+					userId: meId,
+					targetUserId: user.id,
+				}).then(row => row?.memo ?? null),
+				moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
 			} : {}),
 
 			...(opts.detail && isMe ? {
diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts
index 1688bb2ba7cc7935b2fa2ae21b2a994d4e5e56d3..bb190cf60fa581d7240adb1f9e931bbbb42dba79 100644
--- a/packages/backend/src/daemons/ServerStatsService.ts
+++ b/packages/backend/src/daemons/ServerStatsService.ts
@@ -40,7 +40,7 @@ export class ServerStatsService implements OnApplicationShutdown {
 			const stats = {
 				cpu: roundCpu(cpu),
 				mem: {
-					used: round(memStats.used - memStats.buffers - memStats.cached),
+					used: round(memStats.total - memStats.available),
 					active: round(memStats.active),
 				},
 				net: {
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index d4b1fb31b107a10f31b6f6da1954d49ade2178cb..c06c7a7159c2114d5718dce8986b895d66f00284 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -1,6 +1,7 @@
 export const DI = {
 	config: Symbol('config'),
 	db: Symbol('db'),
+	meilisearch: Symbol('meilisearch'),
 	redis: Symbol('redis'),
 	redisForPub: Symbol('redisForPub'),
 	redisForSub: Symbol('redisForSub'),
@@ -70,5 +71,6 @@ export const DI = {
 	roleAssignmentsRepository: Symbol('roleAssignmentsRepository'),
 	flashsRepository: Symbol('flashsRepository'),
 	flashLikesRepository: Symbol('flashLikesRepository'),
+	userMemosRepository: Symbol('userMemosRepository'),
 	//#endregion
 };
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index f413246a1fc3ff6c0bc594b574fa4cb56256e3b9..5610929648b147341cb67d3529abb3c2667e8929 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -1,4 +1,4 @@
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { bindThis } from '@/decorators.js';
 
 export class RedisKVCache<T> {
@@ -38,7 +38,7 @@ export class RedisKVCache<T> {
 			await this.redisClient.set(
 				`kvcache:${this.name}:${key}`,
 				this.toRedisConverter(value),
-				'ex', Math.round(this.lifetime / 1000),
+				'EX', Math.round(this.lifetime / 1000),
 			);
 		}
 	}
@@ -122,7 +122,7 @@ export class RedisSingleCache<T> {
 			await this.redisClient.set(
 				`singlecache:${this.name}`,
 				this.toRedisConverter(value),
-				'ex', Math.round(this.lifetime / 1000),
+				'EX', Math.round(this.lifetime / 1000),
 			);
 		}
 	}
diff --git a/packages/backend/src/misc/check-https.ts b/packages/backend/src/misc/check-https.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b33f0199735dc8215c990ab96cf04972b209742b
--- /dev/null
+++ b/packages/backend/src/misc/check-https.ts
@@ -0,0 +1,4 @@
+export function checkHttps(url: string) {
+    return url.startsWith('https://') ||
+        (url.startsWith('http://') && process.env.NODE_ENV !== 'production');
+}
diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts
index e8c66683ccb364cb5bcf06080535a4ad820e2def..910bebfcfe57401fa416012cc50a3df2cde80b5c 100644
--- a/packages/backend/src/misc/check-word-mute.ts
+++ b/packages/backend/src/misc/check-word-mute.ts
@@ -1,3 +1,4 @@
+import { AhoCorasick } from 'slacc';
 import RE2 from 're2';
 import type { Note } from '@/models/entities/Note.js';
 import type { User } from '@/models/entities/User.js';
@@ -12,6 +13,8 @@ type UserLike = {
 	id: User['id'];
 };
 
+const acCache = new Map<string, AhoCorasick>();
+
 export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array<string | string[]>): Promise<boolean> {
 	// 自分自身
 	if (me && (note.userId === me.id)) return false;
@@ -21,7 +24,22 @@ export async function checkWordMute(note: NoteLike, me: UserLike | null | undefi
 
 		if (text === '') return false;
 
-		const matched = mutedWords.some(filter => {
+		const acable = mutedWords.filter(filter => Array.isArray(filter) && filter.length === 1).map(filter => filter[0]).sort();
+		const unacable = mutedWords.filter(filter => !Array.isArray(filter) || filter.length !== 1);
+		const acCacheKey = acable.join('\n');
+		const ac = acCache.get(acCacheKey) ?? AhoCorasick.withPatterns(acable);
+		acCache.delete(acCacheKey);
+		for (const obsoleteKeys of acCache.keys()) {
+			if (acCache.size > 1000) {
+				acCache.delete(obsoleteKeys);
+			}
+		}
+		acCache.set(acCacheKey, ac);
+		if (ac.isMatch(text)) {
+			return true;
+		}
+
+		const matched = unacable.some(filter => {
 			if (Array.isArray(filter)) {
 				return filter.every(keyword => text.includes(keyword));
 			} else {
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 7be7b8190455137e9e7d5ca0c1b4a59b4b27958e..588c98b58dd8a3baddeb88532cb531ffe4879fc6 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -388,6 +388,12 @@ const $roleAssignmentsRepository: Provider = {
 	inject: [DI.db],
 };
 
+const $userMemosRepository: Provider = {
+	provide: DI.userMemosRepository,
+	useFactory: (db: DataSource) => db.getRepository(UserMemo),
+	inject: [DI.db],
+};
+
 @Module({
 	imports: [
 	],
@@ -456,6 +462,7 @@ const $roleAssignmentsRepository: Provider = {
 		$roleAssignmentsRepository,
 		$flashsRepository,
 		$flashLikesRepository,
+		$userMemosRepository,
 	],
 	exports: [
 		$usersRepository,
@@ -522,6 +529,7 @@ const $roleAssignmentsRepository: Provider = {
 		$roleAssignmentsRepository,
 		$flashsRepository,
 		$flashLikesRepository,
+		$userMemosRepository,
 	],
 })
 export class RepositoryModule {}
diff --git a/packages/backend/src/models/entities/Channel.ts b/packages/backend/src/models/entities/Channel.ts
index 2d346fdf9d4c6dd28835415bb4c3a96f258e5928..d7c4583da34cfd992619997ba2dcda8aed6fa457 100644
--- a/packages/backend/src/models/entities/Channel.ts
+++ b/packages/backend/src/models/entities/Channel.ts
@@ -64,6 +64,18 @@ export class Channel {
 	})
 	public pinnedNoteIds: string[];
 
+	@Column('varchar', {
+		length: 16,
+		default: '#86b300',
+	})
+	public color: string;
+
+	@Index()
+	@Column('boolean', {
+		default: false,
+	})
+	public isArchived: boolean;
+
 	@Index()
 	@Column('integer', {
 		default: 0,
diff --git a/packages/backend/src/models/entities/Meta.ts b/packages/backend/src/models/entities/Meta.ts
index 2e4f90b57f8d6e56adc1a6327c9733e1f910aef7..6d44e4edc7664c2964cb7f3119301a48e2d12df7 100644
--- a/packages/backend/src/models/entities/Meta.ts
+++ b/packages/backend/src/models/entities/Meta.ts
@@ -405,4 +405,16 @@ export class Meta {
 		default: { },
 	})
 	public policies: Record<string, any>;
+
+	@Column('varchar', {
+		length: 280,
+		array: true,
+		default: '{}',
+	})
+	public serverRules: string[];
+
+	@Column('varchar', {
+		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[];
 }
diff --git a/packages/backend/src/models/entities/Role.ts b/packages/backend/src/models/entities/Role.ts
index eca9bcf2706d5ab54e182968cfff1859b27596ea..61f40d59dabdbef6c32b12247780fcf3cc7095fb 100644
--- a/packages/backend/src/models/entities/Role.ts
+++ b/packages/backend/src/models/entities/Role.ts
@@ -151,6 +151,11 @@ export class Role {
 	})
 	public isAdministrator: boolean;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public isExplorable: boolean;
+
 	@Column('boolean', {
 		default: false,
 	})
diff --git a/packages/backend/src/models/entities/User.ts b/packages/backend/src/models/entities/User.ts
index 04dfa2110725450aba2dff8bef01e18b99fb78c7..8e10f999b617e665a536a9b3688d808dd44bbdcf 100644
--- a/packages/backend/src/models/entities/User.ts
+++ b/packages/backend/src/models/entities/User.ts
@@ -75,6 +75,12 @@ export class User {
 	})
 	public movedToUri: string | null;
 
+	@Column('timestamp with time zone', {
+		nullable: true,
+		comment: 'When the user moved to another account',
+	})
+	public movedAt: Date | null;
+
 	@Column('simple-array', {
 		nullable: true,
 		comment: 'URIs the user is known as too',
@@ -253,11 +259,23 @@ export type LocalUser = User & {
 	uri: null;
 }
 
+export type PartialLocalUser = Partial<User> & {
+	id: User['id'];
+	host: null;
+	uri: null;
+}
+
 export type RemoteUser = User & {
 	host: string;
 	uri: string;
 }
 
+export type PartialRemoteUser = Partial<User> & {
+	id: User['id'];
+	host: string;
+	uri: string;
+}
+
 export const localUsernameSchema = { type: 'string', pattern: /^\w{1,20}$/.toString().slice(1, -1) } as const;
 export const passwordSchema = { type: 'string', minLength: 1 } as const;
 export const nameSchema = { type: 'string', minLength: 1, maxLength: 50 } as const;
diff --git a/packages/backend/src/models/entities/UserMemo.ts b/packages/backend/src/models/entities/UserMemo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7dc34b434696c21ee75fa6a4453e73a1fabbe4a4
--- /dev/null
+++ b/packages/backend/src/models/entities/UserMemo.ts
@@ -0,0 +1,42 @@
+import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
+import { id } from '../id.js';
+import { User } from './User.js';
+
+@Entity()
+@Index(['userId', 'targetUserId'], { unique: true })
+export class UserMemo {
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Index()
+	@Column({
+		...id(),
+		comment: 'The ID of author.',
+	})
+	public userId: User['id'];
+
+	@ManyToOne(type => User, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user: User | null;
+
+	@Index()
+	@Column({
+		...id(),
+		comment: 'The ID of target user.',
+	})
+	public targetUserId: User['id'];
+
+	@ManyToOne(type => User, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public targetUser: User | null;
+
+	@Column('varchar', {
+		length: 2048,
+		comment: 'Memo.',
+	})
+	public memo: string;
+}
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index 48d6e15f2a319b55c6706c80367e35d7c1026345..b8ba28db9b58fc2a69c550957e681a1b407e9a63 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -55,6 +55,7 @@ import { UserPending } from '@/models/entities/UserPending.js';
 import { UserProfile } from '@/models/entities/UserProfile.js';
 import { UserPublickey } from '@/models/entities/UserPublickey.js';
 import { UserSecurityKey } from '@/models/entities/UserSecurityKey.js';
+import { UserMemo } from '@/models/entities/UserMemo.js';
 import { Webhook } from '@/models/entities/Webhook.js';
 import { Channel } from '@/models/entities/Channel.js';
 import { RetentionAggregation } from '@/models/entities/RetentionAggregation.js';
@@ -129,6 +130,7 @@ export {
 	RoleAssignment,
 	Flash,
 	FlashLike,
+	UserMemo,
 };
 
 export type AbuseUserReportsRepository = Repository<AbuseUserReport>;
@@ -195,3 +197,4 @@ export type RolesRepository = Repository<Role>;
 export type RoleAssignmentsRepository = Repository<RoleAssignment>;
 export type FlashsRepository = Repository<Flash>;
 export type FlashLikesRepository = Repository<FlashLike>;
+export type UserMemoRepository = Repository<UserMemo>;
diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts
index 745b39a6ba1e35344a0807cc326047d92b70d693..fd61a70c0ecca3274b56ec92c031f4552ead6d53 100644
--- a/packages/backend/src/models/json-schema/channel.ts
+++ b/packages/backend/src/models/json-schema/channel.ts
@@ -30,6 +30,10 @@ export const packedChannelSchema = {
 			format: 'url',
 			nullable: true, optional: false,
 		},
+		isArchived: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
 		notesCount: {
 			type: 'number',
 			nullable: false, optional: false,
@@ -59,5 +63,9 @@ export const packedChannelSchema = {
 				format: 'id',
 			},
 		},
+		color: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index 7d40979e3d5c6018d398a562cbf674a08955e2fa..529c1303d14ad2ac6d99bf142b225505b8359752 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -80,9 +80,14 @@ export const packedUserDetailedNotMeOnlySchema = {
 		},
 		alsoKnownAs: {
 			type: 'array',
-			format: 'uri',
 			nullable: true,
 			optional: false,
+			items: {
+				type: 'string',
+				format: 'id',
+				nullable: false,
+				optional: false,
+			},
 		},
 		createdAt: {
 			type: 'string',
@@ -143,6 +148,7 @@ export const packedUserDetailedNotMeOnlySchema = {
 		fields: {
 			type: 'array',
 			nullable: false, optional: false,
+			maxItems: 16,
 			items: {
 				type: 'object',
 				nullable: false, optional: false,
@@ -156,7 +162,6 @@ export const packedUserDetailedNotMeOnlySchema = {
 						nullable: false, optional: false,
 					},
 				},
-				maxLength: 4,
 			},
 		},
 		followersCount: {
@@ -250,6 +255,10 @@ export const packedUserDetailedNotMeOnlySchema = {
 			type: 'boolean',
 			nullable: false, optional: true,
 		},
+		memo: {
+			type: 'string',
+			nullable: false, optional: true,
+		},
 		//#endregion
 	},
 } as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index bb21ed827ee7fd1c73a74cf3bca5b49d7b5dbeb6..f3d404e6c9b4f4197b57ef72b1d701deb34fb92f 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -70,6 +70,7 @@ import { Role } from '@/models/entities/Role.js';
 import { RoleAssignment } from '@/models/entities/RoleAssignment.js';
 import { Flash } from '@/models/entities/Flash.js';
 import { FlashLike } from '@/models/entities/FlashLike.js';
+import { UserMemo } from '@/models/entities/UserMemo.js';
 
 import { Config } from '@/config.js';
 import MisskeyLogger from '@/logger.js';
@@ -183,6 +184,7 @@ export const entities = [
 	RoleAssignment,
 	Flash,
 	FlashLike,
+	UserMemo,
 	...charts,
 ];
 
diff --git a/packages/backend/src/queue/DbQueueProcessorsService.ts b/packages/backend/src/queue/DbQueueProcessorsService.ts
index 233a36dd04beb13a1dffe42102a466dcc0698596..df8ac3a301b44a8f265680a068d5769666a63a10 100644
--- a/packages/backend/src/queue/DbQueueProcessorsService.ts
+++ b/packages/backend/src/queue/DbQueueProcessorsService.ts
@@ -9,11 +9,13 @@ import { ExportFollowingProcessorService } from './processors/ExportFollowingPro
 import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
 import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
 import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
+import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
 import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
 import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
 import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
 import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
 import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
+import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
 import { DeleteAccountProcessorService } from './processors/DeleteAccountProcessorService.js';
 import { ExportFavoritesProcessorService } from './processors/ExportFavoritesProcessorService.js';
 import type Bull from 'bull';
@@ -32,11 +34,13 @@ export class DbQueueProcessorsService {
 		private exportMutingProcessorService: ExportMutingProcessorService,
 		private exportBlockingProcessorService: ExportBlockingProcessorService,
 		private exportUserListsProcessorService: ExportUserListsProcessorService,
+		private exportAntennasProcessorService: ExportAntennasProcessorService,
 		private importFollowingProcessorService: ImportFollowingProcessorService,
 		private importMutingProcessorService: ImportMutingProcessorService,
 		private importBlockingProcessorService: ImportBlockingProcessorService,
 		private importUserListsProcessorService: ImportUserListsProcessorService,
 		private importCustomEmojisProcessorService: ImportCustomEmojisProcessorService,
+		private importAntennasProcessorService: ImportAntennasProcessorService,
 		private deleteAccountProcessorService: DeleteAccountProcessorService,
 	) {
 	}
@@ -51,6 +55,7 @@ export class DbQueueProcessorsService {
 		q.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
 		q.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
 		q.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
+		q.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
 		q.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
 		q.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
 		q.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
@@ -58,6 +63,7 @@ export class DbQueueProcessorsService {
 		q.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
 		q.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
 		q.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
+		q.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
 		q.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
 	}
 }
diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts
index 4db9b3854769224abe5d4bc7c83b7119f36e2f6c..3d4cc773219cd23b32755c04e6fe76cd1258574c 100644
--- a/packages/backend/src/queue/QueueProcessorModule.ts
+++ b/packages/backend/src/queue/QueueProcessorModule.ts
@@ -24,11 +24,13 @@ import { ExportFollowingProcessorService } from './processors/ExportFollowingPro
 import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
 import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
 import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
+import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
 import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
 import { ImportCustomEmojisProcessorService } from './processors/ImportCustomEmojisProcessorService.js';
 import { ImportFollowingProcessorService } from './processors/ImportFollowingProcessorService.js';
 import { ImportMutingProcessorService } from './processors/ImportMutingProcessorService.js';
 import { ImportUserListsProcessorService } from './processors/ImportUserListsProcessorService.js';
+import { ImportAntennasProcessorService } from './processors/ImportAntennasProcessorService.js';
 import { ResyncChartsProcessorService } from './processors/ResyncChartsProcessorService.js';
 import { TickChartsProcessorService } from './processors/TickChartsProcessorService.js';
 import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
@@ -55,11 +57,13 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
 		ExportMutingProcessorService,
 		ExportBlockingProcessorService,
 		ExportUserListsProcessorService,
+		ExportAntennasProcessorService,
 		ImportFollowingProcessorService,
 		ImportMutingProcessorService,
 		ImportBlockingProcessorService,
 		ImportUserListsProcessorService,
 		ImportCustomEmojisProcessorService,
+		ImportAntennasProcessorService,
 		DeleteAccountProcessorService,
 		DeleteFileProcessorService,
 		CleanRemoteFilesProcessorService,
diff --git a/packages/backend/src/queue/RelationshipQueueProcessorsService.ts b/packages/backend/src/queue/RelationshipQueueProcessorsService.ts
index af086fa4e74dc86422d0a5b8ff2fb468379eaf05..736b4fa80dda58c63a2cf74ef6bbb19da68aff26 100644
--- a/packages/backend/src/queue/RelationshipQueueProcessorsService.ts
+++ b/packages/backend/src/queue/RelationshipQueueProcessorsService.ts
@@ -17,7 +17,7 @@ export class RelationshipQueueProcessorsService {
 
 	@bindThis
 	public start(q: Bull.Queue): void {
-		const maxJobs = (this.config.deliverJobConcurrency ?? 128) / 4; // conservative?
+		const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
 		q.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
 		q.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
 		q.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 0e99b7bcd295d397f33d4f9703f92eafd43b211a..f293bd4d7e016c5d33e4719f851dd27fc4a35730 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -79,10 +79,7 @@ export class DeliverProcessorService {
 			// Update stats
 			this.federatedInstanceService.fetch(host).then(i => {
 				if (i.isNotResponding) {
-					this.instancesRepository.update(i.id, {
-						isNotResponding: false,
-					});
-					this.federatedInstanceService.updateCachePartial(host, {
+					this.federatedInstanceService.update(i.id, {
 						isNotResponding: false,
 					});
 				}
@@ -101,10 +98,7 @@ export class DeliverProcessorService {
 			// Update stats
 			this.federatedInstanceService.fetch(host).then(i => {
 				if (!i.isNotResponding) {
-					this.instancesRepository.update(i.id, {
-						isNotResponding: true,
-					});
-					this.federatedInstanceService.updateCachePartial(host, {
+					this.federatedInstanceService.update(i.id, {
 						isNotResponding: true,
 					});
 				}
@@ -123,10 +117,7 @@ export class DeliverProcessorService {
 					// 相手が閉鎖していることを明示しているため、配送停止する
 					if (job.data.isSharedInbox && res.statusCode === 410) {
 						this.federatedInstanceService.fetch(host).then(i => {
-							this.instancesRepository.update(i.id, {
-								isSuspended: true,
-							});
-							this.federatedInstanceService.updateCachePartial(host, {
+							this.federatedInstanceService.update(i.id, {
 								isSuspended: true,
 							});
 						});
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..894903e79b1073f351731652c512df3d218b3ac4
--- /dev/null
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -0,0 +1,103 @@
+import fs from 'node:fs';
+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, User } from '@/models/index.js';
+import type { Config } from '@/config.js';
+import Logger from '@/logger.js';
+import { DriveService } from '@/core/DriveService.js';
+import { bindThis } from '@/decorators.js';
+import { createTemp } from '@/misc/create-temp.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type { DBExportAntennasData } from '../types.js';
+import type Bull from 'bull';
+
+@Injectable()
+export class ExportAntennasProcessorService {
+	private logger: Logger;
+
+	constructor (
+		@Inject(DI.config)
+		private config: Config,
+
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.antennasRepository)
+		private antennsRepository: AntennasRepository,
+
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
+	
+		private driveService: DriveService,
+		private utilityService: UtilityService,
+		private queueLoggerService: QueueLoggerService,
+	) {
+		this.logger = this.queueLoggerService.logger.createSubLogger('export-antennas');
+	}
+
+	@bindThis
+	public async process(job: Bull.Job<DBExportAntennasData>, done: () => void): Promise<void> {
+		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
+		if (user == null) {
+			done();
+			return;
+		}
+		const [path, cleanup] = await createTemp();
+		const stream = fs.createWriteStream(path, { flags: 'a' });
+		const write = (input: string): Promise<void> => {
+			return new Promise((resolve, reject) => {
+				stream.write(input, err => {
+					if (err) {
+						this.logger.error(err);
+						reject();
+					} else {
+						resolve();
+					}
+				});
+			});
+		};
+		try {
+			const antennas = await this.antennsRepository.findBy({ userId: job.data.user.id });
+			write('[');
+			for (const [index, antenna] of antennas.entries()) {
+				let users: User[] | undefined;
+				if (antenna.userListId !== null) {
+					const joinings = await this.userListJoiningsRepository.findBy({ userListId: antenna.userListId });
+					users = await this.usersRepository.findBy({
+						id: In(joinings.map(j => j.userId)),
+					});
+				}
+				write(JSON.stringify({
+					name: antenna.name,
+					src: antenna.src,
+					keywords: antenna.keywords,
+					excludeKeywords: antenna.excludeKeywords,
+					users: antenna.users,
+					userListAccts: typeof users !== 'undefined' ? users.map((u) => {
+						return this.utilityService.getFullApAccount(u.username, u.host); // acct
+					}) : null,
+					caseSensitive: antenna.caseSensitive,
+					withReplies: antenna.withReplies,
+					withFile: antenna.withFile,
+					notify: antenna.notify,
+				}));
+				if (antennas.length - 1 !== index) {
+					write(', ');
+				}
+			}
+			write(']');
+			stream.end();
+
+			const fileName = 'antennas-' + DateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
+			this.logger.succ('Exported to: ' + driveFile.id);
+		} finally {
+			cleanup();
+			done();
+		}
+	}
+}
+
diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
index a020006732801e47b9493a3a73e305b317e8560b..c7b54070d6a786244e7401d81592b8244b75b7a9 100644
--- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
@@ -106,7 +106,7 @@ export class ExportBlockingProcessorService {
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
 		} finally {
diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
index daefcdf2f5be2db7f9ca61cd78abd4cbdaa740f7..f2f2383a8837d8c036fbca4a41629e6881ac7497 100644
--- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
@@ -121,7 +121,7 @@ export class ExportFavoritesProcessorService {
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'favorites-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
-			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
 		} finally {
diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
index 59443de57f35108da682582c9ebfc6cde29cfc98..fa9c1ac1ea94b846fed0b205fae19d6a35bac0a4 100644
--- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
@@ -110,7 +110,7 @@ export class ExportFollowingProcessorService {
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'following-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
 		} finally {
diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
index a2a718b892f3404ab6bd3f6082165a1a0884ee6d..b14bf5f5b18af052e973a23f11e1a8d9beddb0bf 100644
--- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
@@ -110,7 +110,7 @@ export class ExportMutingProcessorService {
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
 		} finally {
diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
index 1aa20d6f1d9480060a2864df67a2a023b4de1309..e4f12ad101054ba6bc27df9e3911aad63937c591 100644
--- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
@@ -117,7 +117,7 @@ export class ExportNotesProcessorService {
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
-			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
 		} finally {
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index ce8ed2f5e8c0e5f7170e46914fb3d163106b504e..54bde44044de147a1b6198e9a4efbf61437a296a 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -86,7 +86,7 @@ export class ExportUserListsProcessorService {
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.csv';
-			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true });
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'csv' });
 
 			this.logger.succ(`Exported to: ${driveFile.id}`);
 		} finally {
diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d06131b8c81b80169f9413556b8beb8de5f2a01c
--- /dev/null
+++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
@@ -0,0 +1,96 @@
+import { Injectable, Inject } from '@nestjs/common';
+import Ajv from 'ajv';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import Logger from '@/logger.js';
+import type { AntennasRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import { DBAntennaImportJobData } from '../types.js';
+import type Bull from 'bull';
+
+const validate = new Ajv().compile({
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+		src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
+		userListAccts: { 
+			type: 'array', 
+			items: {
+				type: 'string',
+			}, 
+			nullable: true,
+		},
+		keywords: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		excludeKeywords: { type: 'array', items: {
+			type: 'array', items: {
+				type: 'string',
+			},
+		} },
+		users: { type: 'array', items: {
+			type: 'string',
+		} },
+		caseSensitive: { type: 'boolean' },
+		withReplies: { type: 'boolean' },
+		withFile: { type: 'boolean' },
+		notify: { type: 'boolean' },
+	},
+	required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'],
+});
+
+@Injectable()
+export class ImportAntennasProcessorService {
+	private logger: Logger;
+
+	constructor (
+		@Inject(DI.antennasRepository)
+		private antennasRepository: AntennasRepository,
+
+		private queueLoggerService: QueueLoggerService,
+		private idService: IdService,
+		private globalEventService: GlobalEventService,
+	) {
+		this.logger = this.queueLoggerService.logger.createSubLogger('import-antennas');
+	}
+
+	@bindThis
+	public async process(job: Bull.Job<DBAntennaImportJobData>, done: () => void): Promise<void> {
+		const now = new Date();
+		try {
+			for (const antenna of job.data.antenna) {
+				if (antenna.keywords.length === 0 || antenna.keywords[0].every(x => x === '')) continue;
+				if (!validate(antenna)) {
+					this.logger.warn('Validation Failed');
+					continue;
+				}
+				const result = await this.antennasRepository.insert({
+					id: this.idService.genId(),
+					createdAt: now,
+					lastUsedAt: now,
+					userId: job.data.user.id,
+					name: antenna.name,
+					src: antenna.src === 'list' && antenna.userListAccts ? 'users' : antenna.src,
+					userListId: null,
+					keywords: antenna.keywords,
+					excludeKeywords: antenna.excludeKeywords,
+					users: (antenna.src === 'list' && antenna.userListAccts !== null ? antenna.userListAccts : antenna.users).filter(Boolean),
+					caseSensitive: antenna.caseSensitive,
+					withReplies: antenna.withReplies,
+					withFile: antenna.withFile,
+					notify: antenna.notify,
+				}).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0]));
+				this.logger.succ('Antenna created: ' + result.id);
+				this.globalEventService.publishInternalEvent('antennaCreated', result);
+			}
+		} catch (err: any) {
+			this.logger.error(err);
+		} finally {
+			done();
+		}
+	}
+}
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index ed7f38d0137242e97504122089d5dad62c7c99da..ab8b1e9e22556d683f9e3b30c926858db22afc57 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -84,9 +84,9 @@ export class InboxProcessorService {
 
 		// HTTP-Signature keyIdを元にDBから取得
 		let authUser: {
-		user: RemoteUser;
-		key: UserPublickey | null;
-	} | null = await this.apDbResolverService.getAuthUserFromKeyId(signature.keyId);
+			user: RemoteUser;
+			key: UserPublickey | null;
+		} | null = await this.apDbResolverService.getAuthUserFromKeyId(signature.keyId);
 
 		// keyIdでわからなければ、activity.actorを元にDBから取得 || activity.actorを元にリモートから取得
 		if (authUser == null) {
@@ -174,13 +174,10 @@ export class InboxProcessorService {
 
 		// Update stats
 		this.federatedInstanceService.fetch(authUser.user.host).then(i => {
-			this.instancesRepository.update(i.id, {
+			this.federatedInstanceService.update(i.id, {
 				latestRequestReceivedAt: new Date(),
 				isNotResponding: false,
 			});
-			this.federatedInstanceService.updateCachePartial(host, {
-				isNotResponding: false,
-			});
 
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
 
diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts
index a5006dcf03bc58c9d5b5040023d6bf4d609fd913..ff454df455508464eb5b424c90593fe1edb7b35c 100644
--- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts
+++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts
@@ -10,6 +10,7 @@ import { QueueLoggerService } from '../QueueLoggerService.js';
 import { RelationshipJobData } from '../types.js';
 import type { UsersRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
+import { LocalUser, RemoteUser } from '@/models/entities/User.js';
 
 @Injectable()
 export class RelationshipProcessorService {
@@ -39,7 +40,7 @@ export class RelationshipProcessorService {
 		const [follower, followee] = await Promise.all([
 			this.usersRepository.findOneByOrFail({ id: job.data.from.id }),
 			this.usersRepository.findOneByOrFail({ id: job.data.to.id }),
-		]);
+		]) as [LocalUser | RemoteUser, LocalUser | RemoteUser];
 		await this.userFollowingService.unfollow(follower, followee, job.data.silent);
 		return 'ok';
 	}
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index 23c973d44902739605bfdead7834cfdc0a5e9fee..776dd3aa128a8ed60328cb8840d40b63816c9903 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -1,3 +1,4 @@
+import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
 import type { DriveFile } from '@/models/entities/DriveFile.js';
 import type { Note } from '@/models/entities/Note.js';
 import type { User } from '@/models/entities/User.js';
@@ -33,12 +34,14 @@ export type DbJobData<T extends keyof DbJobMap> = DbJobMap[T];
 export type DbJobMap = {
 	deleteDriveFiles: DbJobDataWithUser;
 	exportCustomEmojis: DbJobDataWithUser;
+	exportAntennas: DBExportAntennasData;
 	exportNotes: DbJobDataWithUser;
 	exportFavorites: DbJobDataWithUser;
 	exportFollowing: DbExportFollowingData;
 	exportMuting: DbJobDataWithUser;
 	exportBlocking: DbJobDataWithUser;
 	exportUserLists: DbJobDataWithUser;
+	importAntennas: DBAntennaImportJobData;
 	importFollowing: DbUserImportJobData;
 	importFollowingToDb: DbUserImportToDbJobData;
 	importMuting: DbUserImportJobData;
@@ -59,6 +62,10 @@ export type DbExportFollowingData = {
 	excludeInactive: boolean;
 };
 
+export type DBExportAntennasData = {
+	user: ThinUser
+}
+
 export type DbUserDeleteJobData = {
 	user: ThinUser;
 	soft?: boolean;
@@ -69,6 +76,11 @@ export type DbUserImportJobData = {
 	fileId: DriveFile['id'];
 };
 
+export type DBAntennaImportJobData = {
+	user: ThinUser,
+	antenna: Antenna
+}
+
 export type DbUserImportToDbJobData = {
 	user: ThinUser;
 	target: string;
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index e13e9265ab52feb5203e596580b8eec72439611d..e675d9cf1baf751e85e58f7aa082bbebce5d9f73 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -11,7 +11,7 @@ import * as url from '@/misc/prelude/url.js';
 import type { Config } from '@/config.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { QueueService } from '@/core/QueueService.js';
-import type { LocalUser, User } from '@/models/entities/User.js';
+import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
 import { UserKeypairService } from '@/core/UserKeypairService.js';
 import type { Following } from '@/models/entities/Following.js';
 import { countIf } from '@/misc/prelude/array.js';
@@ -630,7 +630,7 @@ export class ActivityPubServerService {
 					id: request.params.followee,
 					host: Not(IsNull()),
 				}),
-			]);
+			]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null];
 
 			if (follower == null || followee == null) {
 				reply.code(404);
@@ -665,7 +665,7 @@ export class ActivityPubServerService {
 					id: followRequest.followeeId,
 					host: Not(IsNull()),
 				}),
-			]);
+			]) as [LocalUser | RemoteUser | null, LocalUser | RemoteUser | null];
 
 			if (follower == null || followee == null) {
 				reply.code(404);
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index 794fa76d9e5cb92ed8fb4dabda4c80e49ec7e484..98329ddffa765c54c0b3b4e4e3bf1a65bad1b1b8 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -297,7 +297,8 @@ export class FileServerService {
 			} else if ('badge' in request.query) {
 				const mask = (await sharpBmp(file.path, file.mime))
 					.resize(96, 96, {
-						fit: 'inside',
+						fit: 'contain',
+						position: 'centre',
 						withoutEnlargement: false,
 					})
 					.greyscale()
@@ -453,7 +454,8 @@ export class FileServerService {
 			fileRole: 'original',
 			file,
 			filename: file.name,
-			mime: file.type,
+			// 古いファイルは修正前のmimeを持っているのでできるだけ修正してあげる
+			mime: this.fileInfoService.fixMime(file.type),
 			ext: null,
 			path,
 		};
diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts
index e722563036668fcedb86ea0c817071df8dcf00c8..9bf8deb221a934f0ee55a74b936061b1d025c5f9 100644
--- a/packages/backend/src/server/WellKnownServerService.ts
+++ b/packages/backend/src/server/WellKnownServerService.ts
@@ -8,6 +8,7 @@ import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
 import type { User } from '@/models/entities/User.js';
 import * as Acct from '@/misc/acct.js';
 import { NodeinfoServerService } from './NodeinfoServerService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import type { FindOptionsWhere } from 'typeorm';
 import { bindThis } from '@/decorators.js';
 import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
@@ -23,6 +24,7 @@ export class WellKnownServerService {
 		private usersRepository: UsersRepository,
 
 		private nodeinfoServerService: NodeinfoServerService,
+		private userEntityService: UserEntityService,
 	) {
 		//this.createServer = this.createServer.bind(this);
 	}
@@ -130,7 +132,7 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
 			const self = {
 				rel: 'self',
 				type: 'application/activity+json',
-				href: `${this.config.url}/users/${user.id}`,
+				href: this.userEntityService.genLocalUserUri(user.id),
 			};
 			const profilePage = {
 				rel: 'http://webfinger.net/rel/profile-page',
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index bf5cb20918f703b1f8506c5c715400fcdd3d1313..e3483c82c67d31fa59c02639927883c7eca56e3b 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -261,6 +261,17 @@ export class ApiCallService implements OnApplicationShutdown {
 			}
 		}
 
+		if (ep.meta.prohibitMoved) {
+			if (user?.movedToUri) {
+				throw new ApiError({
+					message: 'You have moved your account.',
+					code: 'YOUR_ACCOUNT_MOVED',
+					id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
+					httpStatusCode: 403,
+				});
+			}
+		}
+
 		if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
 			const myRoles = await this.roleService.getUserRoles(user!.id);
 			if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 689f90287ecb34da21e5944ea5e1ca094d644f26..ee1aae5b6c787fead3660845c5aad2e8f5e20506 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
 import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
 import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
 import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
+import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
 import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
 import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
 import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
@@ -193,6 +194,7 @@ import * as ep___i_exportMute from './endpoints/i/export-mute.js';
 import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
 import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
 import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
+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';
@@ -201,6 +203,7 @@ 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';
 import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
+import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
 import * as ep___i_notifications from './endpoints/i/notifications.js';
 import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
 import * as ep___i_pages from './endpoints/i/pages.js';
@@ -222,7 +225,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
 import * as ep___i_updateEmail from './endpoints/i/update-email.js';
 import * as ep___i_update from './endpoints/i/update.js';
 import * as ep___i_move from './endpoints/i/move.js';
-import * as ep___i_knownAs from './endpoints/i/known-as.js';
 import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
 import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
 import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
@@ -330,6 +332,7 @@ import * as ep___users_search from './endpoints/users/search.js';
 import * as ep___users_show from './endpoints/users/show.js';
 import * as ep___users_stats from './endpoints/users/stats.js';
 import * as ep___users_achievements from './endpoints/users/achievements.js';
+import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
 import * as ep___fetchRss from './endpoints/fetch-rss.js';
 import * as ep___retention from './endpoints/retention.js';
 import { GetterService } from './GetterService.js';
@@ -364,6 +367,7 @@ const $admin_emoji_list: Provider = { provide: 'ep:admin/emoji/list', useClass:
 const $admin_emoji_removeAliasesBulk: Provider = { provide: 'ep:admin/emoji/remove-aliases-bulk', useClass: ep___admin_emoji_removeAliasesBulk.default };
 const $admin_emoji_setAliasesBulk: Provider = { provide: 'ep:admin/emoji/set-aliases-bulk', useClass: ep___admin_emoji_setAliasesBulk.default };
 const $admin_emoji_setCategoryBulk: Provider = { provide: 'ep:admin/emoji/set-category-bulk', useClass: ep___admin_emoji_setCategoryBulk.default };
+const $admin_emoji_setLicenseBulk: Provider = { provide: 'ep:admin/emoji/set-license-bulk', useClass: ep___admin_emoji_setLicenseBulk.default };
 const $admin_emoji_update: Provider = { provide: 'ep:admin/emoji/update', useClass: ep___admin_emoji_update.default };
 const $admin_federation_deleteAllFiles: Provider = { provide: 'ep:admin/federation/delete-all-files', useClass: ep___admin_federation_deleteAllFiles.default };
 const $admin_federation_refreshRemoteInstanceMetadata: Provider = { provide: 'ep:admin/federation/refresh-remote-instance-metadata', useClass: ep___admin_federation_refreshRemoteInstanceMetadata.default };
@@ -528,6 +532,7 @@ const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_
 const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default };
 const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default };
 const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default };
+const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default };
 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 };
@@ -536,6 +541,7 @@ const $i_importBlocking: Provider = { provide: 'ep:i/import-blocking', useClass:
 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 };
 const $i_importUserLists: Provider = { provide: 'ep:i/import-user-lists', useClass: ep___i_importUserLists.default };
+const $i_importAntennas: Provider = { provide: 'ep:i/import-antennas', useClass: ep___i_importAntennas.default };
 const $i_notifications: Provider = { provide: 'ep:i/notifications', useClass: ep___i_notifications.default };
 const $i_pageLikes: Provider = { provide: 'ep:i/page-likes', useClass: ep___i_pageLikes.default };
 const $i_pages: Provider = { provide: 'ep:i/pages', useClass: ep___i_pages.default };
@@ -557,7 +563,6 @@ const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.defau
 const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
 const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
 const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
-const $i_knownAs: Provider = { provide: 'ep:i/known-as', useClass: ep___i_knownAs.default };
 const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
 const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
 const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
@@ -665,6 +670,7 @@ const $users_search: Provider = { provide: 'ep:users/search', useClass: ep___use
 const $users_show: Provider = { provide: 'ep:users/show', useClass: ep___users_show.default };
 const $users_stats: Provider = { provide: 'ep:users/stats', useClass: ep___users_stats.default };
 const $users_achievements: Provider = { provide: 'ep:users/achievements', useClass: ep___users_achievements.default };
+const $users_updateMemo: Provider = { provide: 'ep:users/update-memo', useClass: ep___users_updateMemo.default };
 const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.default };
 const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
 
@@ -703,6 +709,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$admin_emoji_removeAliasesBulk,
 		$admin_emoji_setAliasesBulk,
 		$admin_emoji_setCategoryBulk,
+		$admin_emoji_setLicenseBulk,
 		$admin_emoji_update,
 		$admin_federation_deleteAllFiles,
 		$admin_federation_refreshRemoteInstanceMetadata,
@@ -867,6 +874,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_exportNotes,
 		$i_exportFavorites,
 		$i_exportUserLists,
+		$i_exportAntennas,
 		$i_favorites,
 		$i_gallery_likes,
 		$i_gallery_posts,
@@ -875,6 +883,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_importFollowing,
 		$i_importMuting,
 		$i_importUserLists,
+		$i_importAntennas,
 		$i_notifications,
 		$i_pageLikes,
 		$i_pages,
@@ -896,7 +905,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_updateEmail,
 		$i_update,
 		$i_move,
-		$i_knownAs,
 		$i_webhooks_create,
 		$i_webhooks_list,
 		$i_webhooks_show,
@@ -1004,6 +1012,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_show,
 		$users_stats,
 		$users_achievements,
+		$users_updateMemo,
 		$fetchRss,
 		$retention,
 	],
@@ -1036,6 +1045,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$admin_emoji_removeAliasesBulk,
 		$admin_emoji_setAliasesBulk,
 		$admin_emoji_setCategoryBulk,
+		$admin_emoji_setLicenseBulk,
 		$admin_emoji_update,
 		$admin_federation_deleteAllFiles,
 		$admin_federation_refreshRemoteInstanceMetadata,
@@ -1200,6 +1210,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_exportNotes,
 		$i_exportFavorites,
 		$i_exportUserLists,
+		$i_exportAntennas,
 		$i_favorites,
 		$i_gallery_likes,
 		$i_gallery_posts,
@@ -1208,6 +1219,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_importFollowing,
 		$i_importMuting,
 		$i_importUserLists,
+		$i_importAntennas,
 		$i_notifications,
 		$i_pageLikes,
 		$i_pages,
@@ -1229,7 +1241,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$i_updateEmail,
 		$i_update,
 		$i_move,
-		$i_knownAs,
 		$i_webhooks_create,
 		$i_webhooks_list,
 		$i_webhooks_show,
@@ -1335,6 +1346,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_show,
 		$users_stats,
 		$users_achievements,
+		$users_updateMemo,
 		$fetchRss,
 		$retention,
 	],
diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts
index 1f8915eccaf522be7d95d33bc741bbf12be67259..fe2db1d66a676a18367895deaee6532a0c9a1594 100644
--- a/packages/backend/src/server/api/RateLimiterService.ts
+++ b/packages/backend/src/server/api/RateLimiterService.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import Limiter from 'ratelimiter';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { DI } from '@/di-symbols.js';
 import type Logger from '@/logger.js';
 import { LoggerService } from '@/core/LoggerService.js';
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index fbabf47aff29aa76f59d809da6bb797236998617..b2bd7d82e7e09a62fa4688e356a9c01dc71e4565 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -1,6 +1,7 @@
 import { Inject, Injectable } from '@nestjs/common';
 import rndstr from 'rndstr';
 import bcrypt from 'bcryptjs';
+import { IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
 import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -15,7 +16,6 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
 import { bindThis } from '@/decorators.js';
 import { SigninService } from './SigninService.js';
 import type { FastifyRequest, FastifyReply } from 'fastify';
-import { IsNull } from 'typeorm';
 
 @Injectable()
 export class SignupApiService {
@@ -137,6 +137,11 @@ export class SignupApiService {
 				throw new FastifyReplyError(400, 'USED_USERNAME');
 			}
 
+			const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
+			if (isPreserved) {
+				throw new FastifyReplyError(400, 'DENIED_USERNAME');
+			}
+
 			const code = rndstr('a-z0-9', 16);
 
 			// Generate hash of password
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 769a4490d61896e93c456cad3526850f0740babf..258e8de034195bb5aa00d02f4bc588ac841a5215 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -1,6 +1,6 @@
 import { EventEmitter } from 'events';
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import * as websocket from 'websocket';
 import { DI } from '@/di-symbols.js';
 import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js';
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index d0fe6a57c1856681c1972596ce207150cd88e809..09bd7cbff455ab12df430e88cc1ed61bd616a66e 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -29,6 +29,7 @@ import * as ep___admin_emoji_list from './endpoints/admin/emoji/list.js';
 import * as ep___admin_emoji_removeAliasesBulk from './endpoints/admin/emoji/remove-aliases-bulk.js';
 import * as ep___admin_emoji_setAliasesBulk from './endpoints/admin/emoji/set-aliases-bulk.js';
 import * as ep___admin_emoji_setCategoryBulk from './endpoints/admin/emoji/set-category-bulk.js';
+import * as ep___admin_emoji_setLicenseBulk from './endpoints/admin/emoji/set-license-bulk.js';
 import * as ep___admin_emoji_update from './endpoints/admin/emoji/update.js';
 import * as ep___admin_federation_deleteAllFiles from './endpoints/admin/federation/delete-all-files.js';
 import * as ep___admin_federation_refreshRemoteInstanceMetadata from './endpoints/admin/federation/refresh-remote-instance-metadata.js';
@@ -193,6 +194,7 @@ import * as ep___i_exportMute from './endpoints/i/export-mute.js';
 import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
 import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
 import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
+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';
@@ -201,6 +203,7 @@ 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';
 import * as ep___i_importUserLists from './endpoints/i/import-user-lists.js';
+import * as ep___i_importAntennas from './endpoints/i/import-antennas.js';
 import * as ep___i_notifications from './endpoints/i/notifications.js';
 import * as ep___i_pageLikes from './endpoints/i/page-likes.js';
 import * as ep___i_pages from './endpoints/i/pages.js';
@@ -222,7 +225,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
 import * as ep___i_updateEmail from './endpoints/i/update-email.js';
 import * as ep___i_update from './endpoints/i/update.js';
 import * as ep___i_move from './endpoints/i/move.js';
-import * as ep___i_knownAs from './endpoints/i/known-as.js';
 import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
 import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
 import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
@@ -330,6 +332,7 @@ import * as ep___users_search from './endpoints/users/search.js';
 import * as ep___users_show from './endpoints/users/show.js';
 import * as ep___users_stats from './endpoints/users/stats.js';
 import * as ep___users_achievements from './endpoints/users/achievements.js';
+import * as ep___users_updateMemo from './endpoints/users/update-memo.js';
 import * as ep___fetchRss from './endpoints/fetch-rss.js';
 import * as ep___retention from './endpoints/retention.js';
 
@@ -362,6 +365,7 @@ const eps = [
 	['admin/emoji/remove-aliases-bulk', ep___admin_emoji_removeAliasesBulk],
 	['admin/emoji/set-aliases-bulk', ep___admin_emoji_setAliasesBulk],
 	['admin/emoji/set-category-bulk', ep___admin_emoji_setCategoryBulk],
+	['admin/emoji/set-license-bulk', ep___admin_emoji_setLicenseBulk],
 	['admin/emoji/update', ep___admin_emoji_update],
 	['admin/federation/delete-all-files', ep___admin_federation_deleteAllFiles],
 	['admin/federation/refresh-remote-instance-metadata', ep___admin_federation_refreshRemoteInstanceMetadata],
@@ -526,6 +530,7 @@ const eps = [
 	['i/export-notes', ep___i_exportNotes],
 	['i/export-favorites', ep___i_exportFavorites],
 	['i/export-user-lists', ep___i_exportUserLists],
+	['i/export-antennas', ep___i_exportAntennas],
 	['i/favorites', ep___i_favorites],
 	['i/gallery/likes', ep___i_gallery_likes],
 	['i/gallery/posts', ep___i_gallery_posts],
@@ -534,6 +539,7 @@ const eps = [
 	['i/import-following', ep___i_importFollowing],
 	['i/import-muting', ep___i_importMuting],
 	['i/import-user-lists', ep___i_importUserLists],
+	['i/import-antennas', ep___i_importAntennas],
 	['i/notifications', ep___i_notifications],
 	['i/page-likes', ep___i_pageLikes],
 	['i/pages', ep___i_pages],
@@ -554,8 +560,7 @@ const eps = [
 	['i/unpin', ep___i_unpin],
 	['i/update-email', ep___i_updateEmail],
 	['i/update', ep___i_update],
-	//['i/move', ep___i_move],
-	//['i/known-as', ep___i_knownAs],
+	['i/move', ep___i_move],
 	['i/webhooks/create', ep___i_webhooks_create],
 	['i/webhooks/list', ep___i_webhooks_list],
 	['i/webhooks/show', ep___i_webhooks_show],
@@ -663,6 +668,7 @@ const eps = [
 	['users/show', ep___users_show],
 	['users/stats', ep___users_stats],
 	['users/achievements', ep___users_achievements],
+	['users/update-memo', ep___users_updateMemo],
 	['fetch-rss', ep___fetchRss],
 	['retention', ep___retention],
 ];
@@ -700,6 +706,12 @@ export interface IEndpointMeta {
 
 	readonly requireRolePolicy?: keyof RolePolicies;
 
+	/**
+	 * 引っ越し済みのユーザーによるリクエストを禁止するか
+	 * 省略した場合は false として解釈されます。
+	 */
+	readonly prohibitMoved?: boolean;
+
 	/**
 	 * エンドポイントのリミテーションに関するやつ
 	 * 省略した場合はリミテーションは無いものとして解釈されます。
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index bac8ae16e590d7dff4ed5f246c19450238a32dd1..8a3541dffea49fababcf8a3599d7e8345b6f6d56 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -52,6 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
 				password: ps.password,
+				ignorePreservedUsernames: true,
 			});
 
 			const res = await this.userEntityService.pack(account, account, {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index 814668294f58036a045c3248c88b1f2ad6b8949c..4aa4ad82b4064b71fa79fa75cce07acd067f77be 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -87,12 +87,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				//const emojis = await q.take(ps.limit).getMany();
 
 				emojis = await q.getMany();
+				const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
 
-				emojis = emojis.filter(emoji =>
-					emoji.name.includes(ps.query!) ||
-					emoji.aliases.some(a => a.includes(ps.query!)) ||
-					emoji.category?.includes(ps.query!));
-
+				if (queryarry) {
+					emojis = emojis.filter(emoji => 
+						queryarry.includes(`:${emoji.name}:`)
+					);
+				} else {
+					emojis = emojis.filter(emoji =>
+						emoji.name.includes(ps.query!) ||
+						emoji.aliases.some(a => a.includes(ps.query!)) ||
+						emoji.category?.includes(ps.query!));
+				}
 				emojis.splice(ps.limit + 1);
 			} else {
 				emojis = await q.take(ps.limit).getMany();
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b90b9757be28ca91e5a03cd584d7b3cabca59232
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
@@ -0,0 +1,37 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+
+export const meta = {
+	tags: ['admin'],
+
+	requireCredential: true,
+	requireRolePolicy: 'canManageCustomEmojis',
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		ids: { type: 'array', items: {
+			type: 'string', format: 'misskey:id',
+		} },
+		license: {
+			type: 'string',
+			nullable: true,
+			description: 'Use `null` to reset the license.',
+		},
+	},
+	required: ['ids'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		private customEmojiService: CustomEmojiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			await this.customEmojiService.setLicenseBulk(ps.ids, ps.license ?? null);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index 54ce095488d834dd2541e5a8c338ed4931af7300..83f729953ada4c7d276cfb49827fe8aa7a490dc7 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -39,9 +39,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			const pairs = await Promise.all(followings.map(f => Promise.all([
 				this.usersRepository.findOneByOrFail({ id: f.followerId }),
 				this.usersRepository.findOneByOrFail({ id: f.followeeId }),
-			])));
+			]).then(([from, to]) => [{ id: from.id }, { id: to.id }])));
 
-			this.queueService.createUnfollowJob(pairs.map(p => ({ to: p[0], from: p[1], silent: true })));
+			this.queueService.createUnfollowJob(pairs.map(p => ({ from: p[0], to: p[1], silent: true })));
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
index 0a529ecb080b67a990e3bef5de93048de6707775..4fd74e591d193b95f3d12e887a03bd9b0cad83b0 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -3,6 +3,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { InstancesRepository } from '@/models/index.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { DI } from '@/di-symbols.js';
+import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -28,6 +29,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private instancesRepository: InstancesRepository,
 
 		private utilityService: UtilityService,
+		private federatedInstanceService: FederatedInstanceService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const instance = await this.instancesRepository.findOneBy({ host: this.utilityService.toPuny(ps.host) });
@@ -36,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				throw new Error('instance not found');
 			}
 
-			this.instancesRepository.update({ host: this.utilityService.toPuny(ps.host) }, {
+			this.federatedInstanceService.update(instance.id, {
 				isSuspended: ps.isSuspended,
 			});
 		});
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index fc318a621a7766d974f2a171911f6db0534a6864..87a2d22ac2d58c285ac72eda0ceeee4a5b6b414c 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -118,6 +118,14 @@ export const meta = {
 					optional: false, nullable: false,
 				},
 			},
+			preservedUsernames: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+					optional: false, nullable: false,
+				},
+			},
 			hcaptchaSecretKey: {
 				type: 'string',
 				optional: true, nullable: true,
@@ -311,6 +319,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				hiddenTags: instance.hiddenTags,
 				blockedHosts: instance.blockedHosts,
 				sensitiveWords: instance.sensitiveWords,
+				preservedUsernames: instance.preservedUsernames,
 				hcaptchaSecretKey: instance.hcaptchaSecretKey,
 				recaptchaSecretKey: instance.recaptchaSecretKey,
 				turnstileSecretKey: instance.turnstileSecretKey,
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts
index 1359894634648ae819f490cfb654ecb3432d2bb1..916172f54aecec056619b7e7336c3616a7fd7d8b 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts
@@ -25,6 +25,7 @@ export const paramDef = {
 		isPublic: { type: 'boolean' },
 		isModerator: { type: 'boolean' },
 		isAdministrator: { type: 'boolean' },
+		isExplorable: { type: 'boolean', default: false }, // optional for backward compatibility
 		asBadge: { type: 'boolean' },
 		canEditMembersByModerator: { type: 'boolean' },
 		displayOrder: { type: 'number' },
@@ -76,12 +77,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				isPublic: ps.isPublic,
 				isAdministrator: ps.isAdministrator,
 				isModerator: ps.isModerator,
+				isExplorable: ps.isExplorable,
 				asBadge: ps.asBadge,
 				canEditMembersByModerator: ps.canEditMembersByModerator,
 				displayOrder: ps.displayOrder,
 				policies: ps.policies,
 			}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
-	
+
 			this.globalEventService.publishInternalEvent('roleCreated', created);
 
 			return await this.roleEntityService.pack(created, me);
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
index 37b68c4c41b6cef7e71e093e25a3860040f8d7c1..467f157a610e253498c89830633824d329cca77c 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
@@ -33,6 +33,7 @@ export const paramDef = {
 		isPublic: { type: 'boolean' },
 		isModerator: { type: 'boolean' },
 		isAdministrator: { type: 'boolean' },
+		isExplorable: { type: 'boolean' },
 		asBadge: { type: 'boolean' },
 		canEditMembersByModerator: { type: 'boolean' },
 		displayOrder: { type: 'number' },
@@ -85,6 +86,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				isPublic: ps.isPublic,
 				isModerator: ps.isModerator,
 				isAdministrator: ps.isAdministrator,
+				isExplorable: ps.isExplorable,
 				asBadge: ps.asBadge,
 				canEditMembersByModerator: ps.canEditMembersByModerator,
 				displayOrder: ps.displayOrder,
diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts
index 9c576dffe95dd3fc97d2713b7f416fc16df183f6..4ef4fdc665afbf5ad95b6cadccceb086293f16f6 100644
--- a/packages/backend/src/server/api/endpoints/admin/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts
@@ -2,7 +2,7 @@ import * as os from 'node:os';
 import si from 'systeminformation';
 import { Inject, Injectable } from '@nestjs/common';
 import { DataSource } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 
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 9d19efbbcfefc8ca5dc9b505744b7499c7c012b1..42229c8f23421e022e6480ed44396b77c3e9813b 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -80,7 +80,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				isSilenced: isSilenced,
 				isSuspended: user.isSuspended,
 				lastActiveDate: user.lastActiveDate,
-				moderationNote: profile.moderationNote,
+				moderationNote: profile.moderationNote ?? '',
 				signins,
 				policies: await this.roleService.getUserPolicies(user.id),
 				roles: await this.roleEntityService.packMany(roles, me),
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 11de29bf834c8fd6e6d376ac0a542d527e9a8ce6..0e94f56cfd3426ede515683fcadc19c9842e9ab1 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -94,6 +94,8 @@ export const paramDef = {
 		enableActiveEmailValidation: { type: 'boolean' },
 		enableChartsForRemoteUser: { type: 'boolean' },
 		enableChartsForFederatedInstances: { type: 'boolean' },
+		serverRules: { type: 'array', items: { type: 'string' } },
+		preservedUsernames: { type: 'array', items: { type: 'string' } },
 	},
 	required: [],
 } as const;
@@ -387,6 +389,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
 			}
 
+			if (ps.serverRules !== undefined) {
+				set.serverRules = ps.serverRules;
+			}
+
+			if (ps.preservedUsernames !== undefined) {
+				set.preservedUsernames = ps.preservedUsernames;
+			}
+
 			await this.metaService.update(set);
 			this.moderationLogService.insertModerationLog(me, 'updateMeta');
 		});
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index b7ce3363a93aeb0414280d6d4e0fead6f2ca24e4..5754a9f12aa5974f23ad7d6c080c454d7e03544e 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 88623ce26a0e36b5bb7999910a988e9c2b6ddb85..dca0f443b7975ebec6420e7b5f2aa24c7d2dea16 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { NotesRepository, AntennasRepository } from '@/models/index.js';
 import { QueryService } from '@/core/QueryService.js';
diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts
index 3f85442131b692f71ba57bc3eb299ba8be7441c3..5f980bdbeb10b27e02134a407a7bce94e3d30012 100644
--- a/packages/backend/src/server/api/endpoints/antennas/update.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/update.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	errors: {
@@ -71,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
-		
+
 		private antennaEntityService: AntennaEntityService,
 		private globalEventService: GlobalEventService,
 	) {
diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts
index dff8a9d10df4cc891e233f878ca974002a505349..69e2f2504c6769ec902548742fa4f65f5d3d25ac 100644
--- a/packages/backend/src/server/api/endpoints/channels/create.ts
+++ b/packages/backend/src/server/api/endpoints/channels/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:channels',
 
 	limit: {
@@ -41,6 +43,7 @@ export const paramDef = {
 		name: { type: 'string', minLength: 1, maxLength: 128 },
 		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
 		bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+		color: { type: 'string', minLength: 1, maxLength: 16 },
 	},
 	required: ['name'],
 } as const;
@@ -78,6 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				name: ps.name,
 				description: ps.description ?? null,
 				bannerId: banner ? banner.id : null,
+				...(ps.color !== undefined ? { color: ps.color } : {}),
 			} as Channel).then(x => this.channelsRepository.findOneByOrFail(x.identifiers[0]));
 
 			return await this.channelEntityService.pack(channel, me);
diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts
index f52b45ccf3f8ee1eb26106aa56e9f925affe1927..c8544273a17890747522fb0ac79a875d2caa743f 100644
--- a/packages/backend/src/server/api/endpoints/channels/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:channels',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts
index d25faae38d94845d7ffeac2beb793b4f4e061960..1a8d1164c7ab0c87f46e46b1996a3a28d1f188f8 100644
--- a/packages/backend/src/server/api/endpoints/channels/featured.ts
+++ b/packages/backend/src/server/api/endpoints/channels/featured.ts
@@ -38,6 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		super(meta, paramDef, async (ps, me) => {
 			const query = this.channelsRepository.createQueryBuilder('channel')
 				.where('channel.lastNotedAt IS NOT NULL')
+				.andWhere('channel.isArchived = FALSE')
 				.orderBy('channel.lastNotedAt', 'DESC');
 
 			const channels = await query.take(10).getMany();
diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts
index 8ab59991c785ab3bfd6bf95e83096b893e293e0f..f3ca66cfd2d609c6295a4076730afe7a224e18d0 100644
--- a/packages/backend/src/server/api/endpoints/channels/follow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/follow.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:channels',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts
index 59df0616be0bc0280870779b320b8c93691995c2..8fae972cb1f1c0ba4c7993fb04a5c3029c2b1830 100644
--- a/packages/backend/src/server/api/endpoints/channels/owned.ts
+++ b/packages/backend/src/server/api/endpoints/channels/owned.ts
@@ -44,7 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder(), ps.sinceId, ps.untilId)
+			const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
+				.andWhere('channel.isArchived = FALSE')
 				.andWhere({ userId: me.id });
 
 			const channels = await query
diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts
index a954ba224cfcb9af4bb3fa8f3ed355bbfd85ed00..a3b40b0bbd853e3223d52b5238df05c09b935c88 100644
--- a/packages/backend/src/server/api/endpoints/channels/search.ts
+++ b/packages/backend/src/server/api/endpoints/channels/search.ts
@@ -46,15 +46,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId);
+			const query = this.queryService.makePaginationQuery(this.channelsRepository.createQueryBuilder('channel'), ps.sinceId, ps.untilId)
+				.andWhere('channel.isArchived = FALSE');
 
-			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) }%` });
-				}));
-			} else {
-				query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
+			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) }%` });
+					}));
+				} else {
+					query.andWhere('channel.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
+				}
 			}
 
 			const channels = await query
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 2491d14235e2d8114823ff410b94f38f8711d73d..c881074bab57afe3755c8de71d152675ebdc2490 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { ChannelsRepository, Note, NotesRepository } from '@/models/index.js';
 import { QueryService } from '@/core/QueryService.js';
diff --git a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
index 0c3f6c4855311db0114bfeae9adfb2a4ca51619a..67fb1ea03e1108353d97ad60d5d2d1292d00dc77 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:channels',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
index 855ba47f8c35c6cfef0f1fd63843f3110b83d749..f46ff9f2865cbacdb75ace00e1667adc9b1e2d57 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:channels',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index 084b3f919e2a6bac508794c85617943171da156d..30d7f8b244e76049f408258e5ce60b5bcd57ccda 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -47,12 +47,14 @@ export const paramDef = {
 		name: { type: 'string', minLength: 1, maxLength: 128 },
 		description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
 		bannerId: { type: 'string', format: 'misskey:id', nullable: true },
+		isArchived: { type: 'boolean', nullable: true },
 		pinnedNoteIds: {
 			type: 'array',
 			items: {
 				type: 'string', format: 'misskey:id',
 			},
 		},
+		color: { type: 'string', minLength: 1, maxLength: 16 },
 	},
 	required: ['channelId'],
 } as const;
@@ -104,6 +106,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				...(ps.name !== undefined ? { name: ps.name } : {}),
 				...(ps.description !== undefined ? { description: ps.description } : {}),
 				...(ps.pinnedNoteIds !== undefined ? { pinnedNoteIds: ps.pinnedNoteIds } : {}),
+				...(ps.color !== undefined ? { color: ps.color } : {}),
+				...(typeof ps.isArchived === 'boolean' ? { isArchived: ps.isArchived } : {}),
 				...(banner ? { bannerId: banner.id } : {}),
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index b9d8dce47a4ffb906f0530b3be4c8080207de5c2..c3561e2a710d47ae08eb856f2f93e44960d86b66 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts
index a770dc986d2deb584e21ce7bcb3fe1feb9323cc4..5395a5c373a97df8c1f486f5788393a136c3594e 100644
--- a/packages/backend/src/server/api/endpoints/clips/create.ts
+++ b/packages/backend/src/server/api/endpoints/clips/create.ts
@@ -12,6 +12,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	res: {
@@ -57,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			if (currentCount > (await this.roleService.getUserPolicies(me.id)).clipLimit) {
 				throw new ApiError(meta.errors.tooManyClips);
 			}
-	
+
 			const clip = await this.clipsRepository.insert({
 				id: this.idService.genId(),
 				createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts
index 6addf743a2728cb43e21ddece5481434fa4b5b25..f08caaf8d7e1e6bb4525d35cb28ae60e5abaab45 100644
--- a/packages/backend/src/server/api/endpoints/clips/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:clip-favorite',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
index 5d88870ed27f57246c6eb1f977d0428bad758726..50c5d758bda343d8d7fd8f6dc7d7ee3c55d08022 100644
--- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
index 244843d50f2febee149dfb610b60f6e2cbb7b70f..3da252a2269ddb6d1631664f9b5a0731295da76b 100644
--- a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:clip-favorite',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index a103c3f7d316abc58c674859376e72dba49b42aa..70f19593539258d89918ff13c7aa4ba3254f00d0 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index b3bdef41d3e3616dac2cc55f9dd9faa12051f19a..a1c1f9325ec7a89a4c16581c17023c7fca079394 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -15,6 +15,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	limit: {
 		duration: ms('1hour'),
 		max: 120,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index 3141e0fc01b8ff651f864817f38461bd39fc835e..3ecbba22b54d5b4e1dce1e838ce69e8dcc693f65 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -40,8 +40,13 @@ export const meta = {
 			code: 'NO_SUCH_FOLDER',
 			id: 'ea8fb7a5-af77-4a08-b608-c0218176cd73',
 		},
+		
+		restrictedByRole: {
+			message: 'This feature is restricted by your role.',
+			code: 'RESTRICTED_BY_ROLE',
+			id: '7f59dccb-f465-75ab-5cf4-3ce44e3282f7',
+		},
 	},
-
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
@@ -77,7 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
-
+			const alwaysMarkNsfw = (await this.roleService.getUserPolicies(me.id)).alwaysMarkNsfw;
 			if (file == null) {
 				throw new ApiError(meta.errors.noSuchFile);
 			}
@@ -93,6 +98,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			if (ps.comment !== undefined) file.comment = ps.comment;
 
+			if (ps.isSensitive !== undefined && ps.isSensitive !== file.isSensitive && alwaysMarkNsfw && !ps.isSensitive) {
+				throw new ApiError(meta.errors.restrictedByRole);
+			}
+
 			if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive;
 
 			if (ps.folderId !== undefined) {
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index cfef7938318f67f346ad31f765cb891bab0985cf..c835587c4aa0ce9ad332914f81179f6a0b0b0379 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -19,6 +19,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:drive',
 } as const;
 
diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts
index f21d9d5c336347a14e7c261a355f5c355431cdfe..3172bdbfdaf5bd906847bf7479d922cdf027d503 100644
--- a/packages/backend/src/server/api/endpoints/flash/create.ts
+++ b/packages/backend/src/server/api/endpoints/flash/create.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:flash',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts
index 5581b8ec6069bd652dab85dc0e54c82659d62863..23de2f397098e54fee844a4432c9664f54c78b06 100644
--- a/packages/backend/src/server/api/endpoints/flash/like.ts
+++ b/packages/backend/src/server/api/endpoints/flash/like.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:flash-likes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/flash/unlike.ts b/packages/backend/src/server/api/endpoints/flash/unlike.ts
index b994f5d3479c1df8c14e9724e915e9b020538dad..696512b06c9e331d96f638cb66a3eef04d5c1459 100644
--- a/packages/backend/src/server/api/endpoints/flash/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/flash/unlike.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:flash-likes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts
index cd4e413a403b5bfe2197963778943289ec982f9b..78dfd4a06a71e1e615c4b310c59d1819454eafc1 100644
--- a/packages/backend/src/server/api/endpoints/flash/update.ts
+++ b/packages/backend/src/server/api/endpoints/flash/update.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:flash',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 411c39110a5b6df743449a291e6a2fd574153029..4ad16de911ecaabcdb0d931939dc979f4070a0dd 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -19,6 +19,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:following',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index cb8b6a2e3eb1d74555e0688bf0c23a9ec8cce74a..ca6bfa7e0f1baa8a236644398960819d072c7673 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:gallery',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index 519e56ed6a0f177292a712bfd86564d01a40fbb7..6ac5fa860650a8480f5c06c8d9f35138b600bc2b 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:gallery-likes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
index cfbedcc4d93b3f1ed043723c1a623bc8b915e28f..513089217dfd7aa0c0272cb90d9f5e5d2e19f435 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:gallery-likes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index f14d644a3aff4b49e3d8cd63d364f758aa1f1ee5..a2a10d8400389e1b79dcd091b2fd4849869ea5d1 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:gallery',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts
index c3f2ea9ea75b73cb3743e4398fee7f8b2b4f0581..dd3549020e6dcaaaed116e7209473a639499be64 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/users.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts
@@ -44,7 +44,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const query = this.usersRepository.createQueryBuilder('user')
-				.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) });
+				.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) })
+				.andWhere('user.isSuspended = FALSE');
 
 			const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5));
 
diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
index 102dae4fb765b4a1f4b827834836b363cb0b9d47..4eef49638504d65efecd40564e45d18b4bc7b5f4 100644
--- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
+++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
@@ -4,6 +4,7 @@ import { AchievementService, ACHIEVEMENT_TYPES } from '@/core/AchievementService
 
 export const meta = {
 	requireCredential: true,
+	prohibitMoved: true,
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/i/export-antennas.ts b/packages/backend/src/server/api/endpoints/i/export-antennas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4182c1b247095c79c27f1681e6021260a32b5fd4
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/export-antennas.ts
@@ -0,0 +1,30 @@
+import { Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+
+export const meta = {
+	secure: true,
+	requireCredential: true,
+	limit: {
+		duration: ms('1hour'),
+		max: 1,
+	},
+} 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 (
+		private queueService: QueueService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			this.queueService.createExportAntennasJob(me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
new file mode 100644
index 0000000000000000000000000000000000000000..efb5ce422349be68d3e1181ad918664084346257
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -0,0 +1,84 @@
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+import type { AntennasRepository, DriveFilesRepository, UsersRepository, Antenna as _Antenna } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
+import { RoleService } from '@/core/RoleService.js';
+import { DownloadService } from '@/core/DownloadService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	secure: true,
+	requireCredential: true,
+	prohibitMoved: true,
+
+	limit: {
+		duration: ms('1hour'),
+		max: 1,
+	},
+	errors: {
+		noSuchFile: {
+			message: 'No such file.',
+			code: 'NO_SUCH_FILE',
+			id: '3b71d086-c3fa-431c-b01d-ded65a777172',
+		},
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: 'e842c379-8ac7-4cf7-b07a-4d4de7e4671c',
+		},
+		emptyFile: {
+			message: 'That file is empty.',
+			code: 'EMPTY_FILE',
+			id: '7f60115d-8d93-4b0f-bd0e-3815dcbb389f',
+		},
+		tooManyAntennas: {
+			message: 'You cannot create antenna any more.',
+			code: 'TOO_MANY_ANTENNAS',
+			id: '600917d4-a4cb-4cc5-8ba8-7ac8ea3c7779',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		fileId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['fileId'],
+} as const;
+
+@Injectable() // eslint-disable-next-line import/no-default-export
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor (
+		@Inject(DI.driveFilesRepository)
+		private driveFilesRepository: DriveFilesRepository,
+		
+		@Inject(DI.antennasRepository)
+		private antennasRepository: AntennasRepository,
+
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		private roleService: RoleService,
+		private queueService: QueueService,
+		private downloadService: DownloadService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const users = await this.usersRepository.findOneBy({ id: me.id });
+			if (users === null) throw new ApiError(meta.errors.noSuchUser);
+			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
+			if (file === null) throw new ApiError(meta.errors.noSuchFile);
+			if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
+			const antennas: (_Antenna & { userListAccts: string[] | null })[] = JSON.parse(await this.downloadService.downloadTextFile(file.url));
+			const currentAntennasCount = await this.antennasRepository.countBy({ userId: me.id });
+			if (currentAntennasCount + antennas.length > (await this.roleService.getUserPolicies(me.id)).antennaLimit) {
+				throw new ApiError(meta.errors.tooManyAntennas);
+			}
+			this.queueService.createImportAntennasJob(me, antennas);
+		});
+	}
+} 
+
+export type Antenna = (_Antenna & { userListAccts: string[] | null })[];
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index 8c1c158ab1286051bc9d04ad0820d5085b2898af..811971591a107d38e4cc4f62218b88e3e762b8fc 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueueService } from '@/core/QueueService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 import type { DriveFilesRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../error.js';
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	prohibitMoved: true,
 
 	limit: {
 		duration: ms('1hour'),
@@ -58,15 +60,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private driveFilesRepository: DriveFilesRepository,
 
 		private queueService: QueueService,
+		private accountMoveService: AccountMoveService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 
 			if (file == null) throw new ApiError(meta.errors.noSuchFile);
 			//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-			if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
 			if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
 
+			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
+				me,
+				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
+				true
+			);
+			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
+
 			this.queueService.createImportBlockingJob(me, file.id);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index 383bdc02b5a64daeed23f707aaddbb4cab787a4b..8af278c8837c75abbbd7c7fa1a6d828a25649d72 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueueService } from '@/core/QueueService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 import type { DriveFilesRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../error.js';
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	prohibitMoved: true,
 	limit: {
 		duration: ms('1hour'),
 		max: 1,
@@ -57,15 +59,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private driveFilesRepository: DriveFilesRepository,
 
 		private queueService: QueueService,
+		private accountMoveService: AccountMoveService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 
 			if (file == null) throw new ApiError(meta.errors.noSuchFile);
 			//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-			if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
 			if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
 
+			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
+				me,
+				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
+				true
+			);
+			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
+
 			this.queueService.createImportFollowingJob(me, file.id);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 345ad916cb49ad8ecdc70c3e9ccb7c96edc01fa2..eb0f9ba474eb46dce07179af07d4369890cbcdba 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueueService } from '@/core/QueueService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 import type { DriveFilesRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../error.js';
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	prohibitMoved: true,
 
 	limit: {
 		duration: ms('1hour'),
@@ -58,15 +60,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private driveFilesRepository: DriveFilesRepository,
 
 		private queueService: QueueService,
+		private accountMoveService: AccountMoveService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 
 			if (file == null) throw new ApiError(meta.errors.noSuchFile);
 			//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-			if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile);
 			if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
 
+			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
+				me,
+				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
+				true
+			);
+			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
+
 			this.queueService.createImportMutingJob(me, file.id);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index 875af7ec2304240bad07f11c2207367c43668037..4568e939019c6919ff7e1dc7c6327d1aa9bb41e7 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueueService } from '@/core/QueueService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
 import type { DriveFilesRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../error.js';
@@ -9,6 +10,7 @@ import { ApiError } from '../../error.js';
 export const meta = {
 	secure: true,
 	requireCredential: true,
+	prohibitMoved: true,
 	limit: {
 		duration: ms('1hour'),
 		max: 1,
@@ -57,15 +59,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private driveFilesRepository: DriveFilesRepository,
 
 		private queueService: QueueService,
+		private accountMoveService: AccountMoveService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 
 			if (file == null) throw new ApiError(meta.errors.noSuchFile);
 			//if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType);
-			if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile);
 			if (file.size === 0) throw new ApiError(meta.errors.emptyFile);
 
+			const checkMoving = await this.accountMoveService.validateAlsoKnownAs(
+				me,
+				(old, src) => !!src.movedAt && src.movedAt.getTime() + 1000 * 60 * 60 * 2 > (new Date()).getTime(),
+				true
+			);
+			if (checkMoving ? file.size > 32 * 1024 * 1024 : file.size > 64 * 1024) throw new ApiError(meta.errors.tooBigFile);
+
 			this.queueService.createImportUserListsJob(me, file.id);
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/known-as.ts b/packages/backend/src/server/api/endpoints/i/known-as.ts
deleted file mode 100644
index 964704d82b057dbe9f8f1b28bd89f19de2922318..0000000000000000000000000000000000000000
--- a/packages/backend/src/server/api/endpoints/i/known-as.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Injectable } from '@nestjs/common';
-import ms from 'ms';
-
-import { User } from '@/models/entities/User.js';
-import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ApiError } from '@/server/api/error.js';
-
-import { AccountMoveService } from '@/core/AccountMoveService.js';
-import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
-
-export const meta = {
-	tags: ['users'],
-
-	secure: true,
-	requireCredential: true,
-
-	limit: {
-		duration: ms('1day'),
-		max: 30,
-	},
-
-	errors: {
-		noSuchUser: {
-			message: 'No such user.',
-			code: 'NO_SUCH_USER',
-			id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
-		},
-		notRemote: {
-			message: 'User is not remote. You can only migrate from other instances.',
-			code: 'NOT_REMOTE',
-			id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
-		},
-		uriNull: {
-			message: 'User ActivityPup URI is null.',
-			code: 'URI_NULL',
-			id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
-		},
-	},
-} as const;
-
-export const paramDef = {
-	type: 'object',
-	properties: {
-		alsoKnownAs: { type: 'string' },
-	},
-	required: ['alsoKnownAs'],
-} as const;
-
-@Injectable()
-export default class extends Endpoint<typeof meta, typeof paramDef> {
-	constructor(
-		private userEntityService: UserEntityService,
-		private remoteUserResolveService: RemoteUserResolveService,
-		private apiLoggerService: ApiLoggerService,
-		private accountMoveService: AccountMoveService,
-	) {
-		super(meta, paramDef, async (ps, me) => {
-			// Check parameter
-			if (!ps.alsoKnownAs) throw new ApiError(meta.errors.noSuchUser);
-
-			let unfiltered = ps.alsoKnownAs;
-			const updates = {} as Partial<User>;
-
-			if (!unfiltered) {
-				updates.alsoKnownAs = null;
-			} else {
-				// Parse user's input into the old account
-				if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
-				if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
-				if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
-
-				const userAddress = unfiltered.split('@');
-				// Retrieve the old account
-				const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
-					this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
-					throw new ApiError(meta.errors.noSuchUser);
-				});
-
-				const toUrl: string | null = knownAs.uri;
-				if (!toUrl) throw new ApiError(meta.errors.uriNull);
-				// Only allow moving from a remote account
-				if (this.userEntityService.isLocalUser(knownAs)) throw new ApiError(meta.errors.notRemote);
-
-				updates.alsoKnownAs = updates.alsoKnownAs?.concat([toUrl]) ?? [toUrl];
-			}
-
-			return await this.accountMoveService.createAlias(me, updates);
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index ac76e1f62093718c38bddba5d722c8f27c7dfb72..261dd527c09ed220952c3d8918ce74dbccb0ad77 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -7,40 +7,35 @@ import { DI } from '@/di-symbols.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { ApiError } from '@/server/api/error.js';
 
+import { LocalUser, RemoteUser } from '@/models/entities/User.js';
+
 import { AccountMoveService } from '@/core/AccountMoveService.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
 import { GetterService } from '@/server/api/GetterService.js';
 import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+
+import * as Acct from '@/misc/acct.js';
 
 export const meta = {
 	tags: ['users'],
 
 	secure: true,
 	requireCredential: true,
+	prohibitMoved: true,
 	limit: {
 		duration: ms('1day'),
 		max: 5,
 	},
 
 	errors: {
-		noSuchMoveTarget: {
-			message: 'No such move target.',
-			code: 'NO_SUCH_MOVE_TARGET',
-			id: 'b5c90186-4ab0-49c8-9bba-a1f76c202ba4',
-		},
-		remoteAccountForbids: {
+		destinationAccountForbids: {
 			message:
-				'Remote account doesn\'t have proper \'Known As\' alias. Did you remember to set it?',
-			code: 'REMOTE_ACCOUNT_FORBIDS',
+				'Destination account doesn\'t have proper \'Known As\' alias, or has already moved.',
+			code: 'DESTINATION_ACCOUNT_FORBIDS',
 			id: 'b5c90186-4ab0-49c8-9bba-a1f766282ba4',
 		},
-		notRemote: {
-			message: 'User is not remote. You can only migrate to other instances.',
-			code: 'NOT_REMOTE',
-			id: '4362f8dc-731f-4ad8-a694-be2a88922a24',
-		},
 		rootForbidden: {
 			message: 'The root can\'t migrate.',
 			code: 'NOT_ROOT_FORBIDDEN',
@@ -84,57 +79,52 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		@Inject(DI.config)
 		private config: Config,
 
-		private userEntityService: UserEntityService,
 		private remoteUserResolveService: RemoteUserResolveService,
 		private apiLoggerService: ApiLoggerService,
 		private accountMoveService: AccountMoveService,
 		private getterService: GetterService,
 		private apPersonService: ApPersonService,
+		private userEntityService: UserEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			// check parameter
-			if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchMoveTarget);
+			if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
 			// abort if user is the root
 			if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
 			// abort if user has already moved
 			if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
 
-			let unfiltered = ps.moveToAccount;
-			if (!unfiltered) throw new ApiError(meta.errors.noSuchMoveTarget);
-
 			// parse user's input into the destination account
-			if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
-			if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
-			if (!unfiltered.includes('@')) throw new ApiError(meta.errors.notRemote);
-
-			const userAddress = unfiltered.split('@');
+			const { username, host } = Acct.parse(ps.moveToAccount);
 			// retrieve the destination account
-			let moveTo = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
+			let moveTo = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => {
 				this.apiLoggerService.logger.warn(`failed to resolve remote user: ${e}`);
-				throw new ApiError(meta.errors.noSuchMoveTarget);
+				throw new ApiError(meta.errors.noSuchUser);
 			});
-			const remoteMoveTo = await this.getterService.getRemoteUser(moveTo.id);
-			if (!remoteMoveTo.uri) throw new ApiError(meta.errors.uriNull);
+			const destination = await this.getterService.getUser(moveTo.id) as LocalUser | RemoteUser;
+			const newUri = this.userEntityService.getUserUri(destination);
 
 			// update local db
-			await this.apPersonService.updatePerson(remoteMoveTo.uri);
+			await this.apPersonService.updatePerson(newUri);
 			// retrieve updated user
-			moveTo = await this.apPersonService.resolvePerson(remoteMoveTo.uri);
-			// only allow moving to a remote account
-			if (this.userEntityService.isLocalUser(moveTo)) throw new ApiError(meta.errors.notRemote);
+			moveTo = await this.apPersonService.resolvePerson(newUri);
 
-			let allowed = false;
-
-			const fromUrl = `${this.config.url}/users/${me.id}`;
 			// make sure that the user has indicated the old account as an alias
-			moveTo.alsoKnownAs?.forEach((elem) => {
-				if (fromUrl.includes(elem)) allowed = true;
-			});
+			const fromUrl = this.userEntityService.genLocalUserUri(me.id);
+			let allowed = false;
+			if (moveTo.alsoKnownAs) {
+				for (const knownAs of moveTo.alsoKnownAs) {
+					if (knownAs.includes(fromUrl)) {
+						allowed = true;
+						break;
+					}
+				}
+			}
 
 			// abort if unintended
-			if (!(allowed && moveTo.uri && fromUrl)) throw new ApiError(meta.errors.remoteAccountForbids);
+			if (!allowed || moveTo.movedToUri) throw new ApiError(meta.errors.destinationAccountForbids);
 
-			return await this.accountMoveService.moveToRemote(me, moveTo);
+			return await this.accountMoveService.moveFromLocal(me, moveTo);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index ba0487f2237f46f0071e4e8e20e27f965eca98bf..e141be764a795b483f00dd4f50a00a39f83c2b8d 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,5 +1,5 @@
 import { Brackets, In } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Inject, Injectable } from '@nestjs/common';
 import type { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotesRepository } from '@/models/index.js';
 import { obsoleteNotificationTypes, notificationTypes } from '@/types.js';
diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts
index d4af00027e09968aadd182c287fc91c0176b8366..2293500945d39add0b3a251b654409987cf56707 100644
--- a/packages/backend/src/server/api/endpoints/i/pin.ts
+++ b/packages/backend/src/server/api/endpoints/i/pin.ts
@@ -8,6 +8,7 @@ export const meta = {
 	tags: ['account', 'notes'],
 
 	requireCredential: true,
+	prohibitMoved: true,
 
 	kind: 'write:account',
 
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index be1c72b207fbf41071624644cab018566348e2d5..6c66300bb7202718c7f913800d85f3ba9edf2586 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -3,6 +3,7 @@ import * as mfm from 'mfm-js';
 import { Inject, Injectable } from '@nestjs/common';
 import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
 import { extractHashtags } from '@/misc/extract-hashtags.js';
+import * as Acct from '@/misc/acct.js';
 import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js';
 import type { User } from '@/models/entities/User.js';
 import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js';
@@ -19,7 +20,10 @@ import { HashtagService } from '@/core/HashtagService.js';
 import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { CacheService } from '@/core/CacheService.js';
+import { AccountMoveService } from '@/core/AccountMoveService.js';
+import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
+import { ApiLoggerService } from '../../ApiLoggerService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -71,6 +75,30 @@ export const meta = {
 			code: 'TOO_MANY_MUTED_WORDS',
 			id: '010665b1-a211-42d2-bc64-8f6609d79785',
 		},
+
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
+		},
+
+		uriNull: {
+			message: 'User ActivityPup URI is null.',
+			code: 'URI_NULL',
+			id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
+		},
+
+		forbiddenToSetYourself: {
+			message: 'You can\'t set yourself as your own alias.',
+			code: 'FORBIDDEN_TO_SET_YOURSELF',
+			id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4',
+		},
+
+		restrictedByRole: {
+			message: 'This feature is restricted by your role.',
+			code: 'RESTRICTED_BY_ROLE',
+			id: '8feff0ba-5ab5-585b-31f4-4df816663fad',
+		}
 	},
 
 	res: {
@@ -129,6 +157,12 @@ export const paramDef = {
 		emailNotificationTypes: { type: 'array', items: {
 			type: 'string',
 		} },
+		alsoKnownAs: {
+			type: 'array',
+			maxItems: 10,
+			uniqueItems: true,
+			items: { type: 'string' },
+		},
 	},
 } as const;
 
@@ -153,6 +187,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private globalEventService: GlobalEventService,
 		private userFollowingService: UserFollowingService,
 		private accountUpdateService: AccountUpdateService,
+		private accountMoveService: AccountMoveService,
+		private remoteUserResolveService: RemoteUserResolveService,
+		private apiLoggerService: ApiLoggerService,
 		private hashtagService: HashtagService,
 		private roleService: RoleService,
 		private cacheService: CacheService,
@@ -208,7 +245,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
 			if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
 			if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail;
-			if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
+			if (typeof ps.alwaysMarkNsfw === 'boolean') {
+				if ((await roleService.getUserPolicies(user.id)).alwaysMarkNsfw) throw new ApiError(meta.errors.restrictedByRole);
+				profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
+			}
 			if (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
 			if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
 
@@ -221,6 +261,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				updates.avatarId = avatar.id;
 				updates.avatarUrl = this.driveFileEntityService.getPublicUrl(avatar, 'avatar');
 				updates.avatarBlurhash = avatar.blurhash;
+			} else if (ps.avatarId === null) {
+				updates.avatarId = null;
+				updates.avatarUrl = null;
+				updates.avatarBlurhash = null;
 			}
 
 			if (ps.bannerId) {
@@ -232,6 +276,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				updates.bannerId = banner.id;
 				updates.bannerUrl = this.driveFileEntityService.getPublicUrl(banner);
 				updates.bannerBlurhash = banner.blurhash;
+			} else if (ps.bannerId === null) {
+				updates.bannerId = null;
+				updates.bannerUrl = null;
+				updates.bannerBlurhash = null;
 			}
 
 			if (ps.pinnedPageId) {
@@ -252,6 +300,38 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 					});
 			}
 
+			if (ps.alsoKnownAs) {
+				if (_user.movedToUri) {
+					throw new ApiError({
+						message: 'You have moved your account.',
+						code: 'YOUR_ACCOUNT_MOVED',
+						id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
+						httpStatusCode: 403,
+					});
+				}
+
+				// Parse user's input into the old account
+				const newAlsoKnownAs = new Set<string>();
+				for (const line of ps.alsoKnownAs) {
+					if (!line) throw new ApiError(meta.errors.noSuchUser);
+					const { username, host } = Acct.parse(line);
+
+					// Retrieve the old account
+					const knownAs = await this.remoteUserResolveService.resolveUser(username, host).catch((e) => {
+						this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`);
+						throw new ApiError(meta.errors.noSuchUser);
+					});
+					if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself);
+
+					const toUrl = this.userEntityService.getUserUri(knownAs);
+					if (!toUrl) throw new ApiError(meta.errors.uriNull);
+
+					newAlsoKnownAs.add(toUrl);
+				}
+
+				updates.alsoKnownAs = newAlsoKnownAs.size > 0 ? Array.from(newAlsoKnownAs) : null;
+			}
+
 			//#region emojis/tags
 
 			let emojis = [] as string[];
@@ -279,6 +359,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			//#endregion
 
 			if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates);
+			if (Object.keys(updates).includes('alsoKnownAs')) {
+				this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates });
+			}
 			if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates);
 
 			const iObj = await this.userEntityService.pack<true, true>(user.id, user, {
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 37974ce2a3f1b03a452d57f27462ad17ba2d81c3..584ea07c3bdb008e6e3542076f38c2033ebfefc9 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -201,10 +201,6 @@ export const meta = {
 						type: 'boolean',
 						optional: false, nullable: false,
 					},
-					elasticsearch: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
 					hcaptcha: {
 						type: 'boolean',
 						optional: false, nullable: false,
@@ -310,6 +306,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 				translatorAvailable: instance.deeplAuthKey != null,
 
+				serverRules: instance.serverRules,
+
 				policies: { ...DEFAULT_POLICIES, ...instance.policies },
 
 				mediaProxy: this.config.mediaProxy,
@@ -329,7 +327,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				response.features = {
 					registration: !instance.disableRegistration,
 					emailRequiredForSignup: instance.emailRequiredForSignup,
-					elasticsearch: this.config.elasticsearch ? true : false,
 					hcaptcha: instance.enableHcaptcha,
 					recaptcha: instance.enableRecaptcha,
 					turnstile: instance.enableTurnstile,
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 6e24e1024d09a9c0dc3d70bb7b6f70753ea42105..ee358d5c6c0ec4d9b16b0d090d506fe173f64a6c 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -11,6 +11,7 @@ export const meta = {
 	tags: ['account'],
 
 	requireCredential: true,
+	prohibitMoved: true,
 
 	kind: 'write:mutes',
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 69fafcb9c77307e108a54f6cb22b1f68cfec53a4..3f7f2cdecef3d4b2a76d4dd81be4271e52b2c056 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -18,6 +18,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	limit: {
 		duration: ms('1hour'),
 		max: 300,
@@ -260,7 +262,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			let channel: Channel | null = null;
 			if (ps.channelId != null) {
-				channel = await this.channelsRepository.findOneBy({ id: ps.channelId });
+				channel = await this.channelsRepository.findOneBy({ id: ps.channelId, isArchived: false });
 
 				if (channel == null) {
 					throw new ApiError(meta.errors.noSuchChannel);
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index 0ce80a1a63e8faefc1da1877e6a51c47e946364f..611ea19560174b405027d426b950104e662e928a 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -12,6 +12,7 @@ export const meta = {
 	tags: ['notes', 'favorites'],
 
 	requireCredential: true,
+	prohibitMoved: true,
 
 	kind: 'write:favorites',
 
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 2a44dc537e8f1a7b6f3b738418ca83a5ee33b2a9..3a33b037f868abe1116fc4cf2373894a282c0724 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -17,6 +17,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:votes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index 04e374d1aed37211a40cc34f5adc01e0b1a2109e..97cb026779c51a56f26d249ad04abbeed7b880c9 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:reactions',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index fb5abd917f9e4a60e8218e6417b05c2985594c23..f6385400c3c778c2be6531332d621569244c8a65 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -1,11 +1,10 @@
 import { Inject, Injectable } from '@nestjs/common';
 import type { NotesRepository } from '@/models/index.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { QueryService } from '@/core/QueryService.js';
+import { SearchService } from '@/core/SearchService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '../../error.js';
 
@@ -43,8 +42,7 @@ export const paramDef = {
 		offset: { type: 'integer', default: 0 },
 		host: {
 			type: 'string',
-			nullable: true,
-			description: 'The local host is represented with `null`.',
+			description: 'The local host is represented with `.`.',
 		},
 		userId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		channelId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
@@ -61,11 +59,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		@Inject(DI.config)
 		private config: Config,
 	
-		@Inject(DI.notesRepository)
-		private notesRepository: NotesRepository,
-
 		private noteEntityService: NoteEntityService,
-		private queryService: QueryService,
+		private searchService: SearchService,
 		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
@@ -74,27 +69,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				throw new ApiError(meta.errors.unavailable);
 			}
 	
-			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
-
-			if (ps.userId) {
-				query.andWhere('note.userId = :userId', { userId: ps.userId });
-			} else if (ps.channelId) {
-				query.andWhere('note.channelId = :channelId', { channelId: ps.channelId });
-			}
-
-			query
-				.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
-				.innerJoinAndSelect('note.user', 'user')
-				.leftJoinAndSelect('note.reply', 'reply')
-				.leftJoinAndSelect('note.renote', 'renote')
-				.leftJoinAndSelect('reply.user', 'replyUser')
-				.leftJoinAndSelect('renote.user', 'renoteUser');
-
-			this.queryService.generateVisibilityQuery(query, me);
-			if (me) this.queryService.generateMutedUserQuery(query, me);
-			if (me) this.queryService.generateBlockedUserQuery(query, me);
-
-			const notes = await query.take(ps.limit).getMany();
+			const notes = await this.searchService.searchNote(ps.query, me, {
+				userId: ps.userId,
+				channelId: ps.channelId,
+				host: ps.host,
+			}, {
+				untilId: ps.untilId,
+				sinceId: ps.sinceId,
+				limit: ps.limit,
+			});
 
 			return await this.noteEntityService.packMany(notes, me);
 		});
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 4015bf1f29df90bddc364be59fac9dc5493906f8..e08ab399f88f2b1dd41f1903b5102d9cca3a842f 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:pages',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index d27990f7e13c28c63aadc151bfd75b25b1969c8c..543c126d9cf937701c07e89e6222ed431efe7278 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -10,6 +10,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:page-likes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts
index e397e2a23b81d48977c57931bc934cfefd077ff5..f0c0198460146a74fba76c0e143ee0b635711d38 100644
--- a/packages/backend/src/server/api/endpoints/pages/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts
@@ -9,6 +9,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:page-likes',
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index 35b402ec568cb463a031dd7fedc2d22505a73caf..751274067ed276f79734aa6ad77e22c67e2e7e67 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -11,6 +11,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:pages',
 
 	limit: {
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index b285269617fa39e40ece35eea8a5fc4806b94826..beb5850d7883e6c205bd7d05b78be26e92a67700 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -13,6 +13,7 @@ export const meta = {
 	tags: ['account'],
 
 	requireCredential: true,
+	prohibitMoved: true,
 
 	kind: 'write:mutes',
 
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index 655dd7cd83d8332696b3574f0e2cd4be1fc4fc2c..4ced6d3ff175ac1f6325e3127644e712a6ba2c1a 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { DataSource } from 'typeorm';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import { resetDb } from '@/misc/reset-db.js';
diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
index b45d4af1fe05373d147bda86e6558f5853558c39..6202c740f1a3e3099b3a6dcbbe693dd2b4625e58 100644
--- a/packages/backend/src/server/api/endpoints/roles/notes.ts
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import Redis from 'ioredis';
+import * as Redis from 'ioredis';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { NotesRepository, RolesRepository } from '@/models/index.js';
 import { QueryService } from '@/core/QueryService.js';
@@ -65,12 +65,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		super(meta, paramDef, async (ps, me) => {
 			const role = await this.rolesRepository.findOneBy({
 				id: ps.roleId,
+				isPublic: true,
 			});
 
 			if (role == null) {
 				throw new ApiError(meta.errors.noSuchRole);
 			}
-
+			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}`,
diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts
index c80b6efdcda90672bd8dfa91766c89bb29ca004d..6293c5cb501cbe3f89241b345fec57fc9635c80a 100644
--- a/packages/backend/src/server/api/endpoints/username/available.ts
+++ b/packages/backend/src/server/api/endpoints/username/available.ts
@@ -4,6 +4,7 @@ import type { UsedUsernamesRepository, UsersRepository } from '@/models/index.js
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { localUsernameSchema } from '@/models/entities/User.js';
 import { DI } from '@/di-symbols.js';
+import { MetaService } from '@/core/MetaService.js';
 
 export const meta = {
 	tags: ['users'],
@@ -39,9 +40,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 		@Inject(DI.usedUsernamesRepository)
 		private usedUsernamesRepository: UsedUsernamesRepository,
+
+		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			// Get exist
 			const exist = await this.usersRepository.countBy({
 				host: IsNull(),
 				usernameLower: ps.username.toLowerCase(),
@@ -49,8 +51,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			const exist2 = await this.usedUsernamesRepository.countBy({ username: ps.username.toLowerCase() });
 
+			const meta = await this.metaService.fetch();
+			const isPreserved = meta.preservedUsernames.map(x => x.toLowerCase()).includes(ps.username.toLowerCase());
+
 			return {
-				available: exist === 0 && exist2 === 0,
+				available: exist === 0 && exist2 === 0 && !isPreserved,
 			};
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts
index 8becb68a34f32efdeda55c270975974bac417e03..28cd9f6ce58d910e4b82a015c1a0125f4fc72370 100644
--- a/packages/backend/src/server/api/endpoints/users.ts
+++ b/packages/backend/src/server/api/endpoints/users.ts
@@ -50,8 +50,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const query = this.usersRepository.createQueryBuilder('user');
-			query.where('user.isExplorable = TRUE');
+			const query = this.usersRepository.createQueryBuilder('user')
+				.where('user.isExplorable = TRUE')
+				.andWhere('user.isSuspended = FALSE');
 
 			switch (ps.state) {
 				case 'alive': query.andWhere('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break;
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts
index a840c1a04e27d5a473073848b98da2a1afa5152f..751088952634c02500a72bd380313ebc722efa52 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts
@@ -13,6 +13,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	description: 'Create a new list of users.',
@@ -58,7 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
 				throw new ApiError(meta.errors.tooManyUserLists);
 			}
-	
+
 			const userList = await this.userListsRepository.insert({
 				id: this.idService.genId(),
 				createdAt: new Date(),
diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
index d2dd5731eebe6bd4e16d29eadb79ab1d72ab370d..d50b70efc2fb9dc3c1108199f6adda0a1960eb55 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
@@ -12,6 +12,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	description: 'Remove a user from a list.',
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 1c1fdc23f16f26716e8da2fcac238396850044d0..925037e48420321e5044c01d82133d9b06fec149 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -12,6 +12,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	kind: 'write:account',
 
 	description: 'Add a user to an existing list.',
diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ca7756ef750cd6824bce8601330815413c7f212a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts
@@ -0,0 +1,85 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { IdService } from '@/core/IdService.js';
+import type { UserMemoRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	tags: ['account'],
+
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '6fef56f3-e765-4957-88e5-c6f65329b8a5',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+		memo: {
+			type: 'string',
+			nullable: true,
+			description: 'A personal memo for the target user. If null or empty, delete the memo.',
+		},
+	},
+	required: ['userId', 'memo'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.userMemosRepository)
+		private userMemosRepository: UserMemoRepository,
+		private getterService: GetterService,
+		private idService: IdService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			// Get target
+			const target = await this.getterService.getUser(ps.userId).catch(err => {
+				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+				throw err;
+			});
+
+			// 引数がnullか空文字であれば、パーソナルメモを削除する
+			if (ps.memo === '' || ps.memo == null) {
+				await this.userMemosRepository.delete({
+					userId: me.id,
+					targetUserId: target.id,
+				});
+				return;
+			}
+
+			// 以前に作成されたパーソナルメモがあるかどうか確認
+			const previousMemo = await this.userMemosRepository.findOneBy({
+				userId: me.id,
+				targetUserId: target.id,
+			});
+
+			if (!previousMemo) {
+				await this.userMemosRepository.insert({
+					id: this.idService.genId(),
+					userId: me.id,
+					targetUserId: target.id,
+					memo: ps.memo,
+				});
+			} else {
+				await this.userMemosRepository.update(previousMemo.id, {
+					userId: me.id,
+					targetUserId: target.id,
+					memo: ps.memo,
+				});
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index b3e193cd34f934734eae1cb80c507284adc72377..e61e92c623f227289d2e40306f32f3c5baf45dfb 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -70,10 +70,10 @@ export class UrlPreviewService {
 				await summaly(url, {
 					followRedirects: false,
 					lang: lang ?? 'ja-JP',
-					agent: {
+					agent: this.config.proxy ? {
 						http: this.httpRequestService.httpAgent,
 						https: this.httpRequestService.httpsAgent,
-					},
+					} : undefined,
 				});
 
 			this.logger.succ(`Got preview of ${url}: ${summary.title}`);
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index d772ac31849280c89255f7135acd5ba43edf87ab..cb5d05a403910528b38b53b111f34a2d12e023af 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -36,7 +36,7 @@ html
 		link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
 		link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')
 		//- https://github.com/misskey-dev/misskey/issues/9842
-		link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.12.0')
+		link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.17.0')
 		link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
 
 		if !config.clientManifestExists
diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts
index afb72c84d40e4bdcc89851c8cb9ec3c2f9ade48e..f885209b7f163b3d5c188cde6cd5302d730d790a 100644
--- a/packages/backend/test/e2e/endpoints.ts
+++ b/packages/backend/test/e2e/endpoints.ts
@@ -4,8 +4,9 @@ import * as assert from 'assert';
 // node-fetch only supports it's own Blob yet
 // https://github.com/node-fetch/node-fetch/pull/1664
 import { Blob } from 'node-fetch';
-import { startServer, signup, post, api, uploadFile, simpleGet } from '../utils.js';
+import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js';
 import type { INestApplicationContext } from '@nestjs/common';
+import { User } from '@/models/index.js';
 
 describe('Endpoints', () => {
 	let app: INestApplicationContext;
@@ -289,6 +290,16 @@ describe('Endpoints', () => {
 			}, bob);
 
 			assert.strictEqual(res.status, 200);
+
+			const connection = await initTestDb(true);
+			const Users = connection.getRepository(User);
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.followersCount, 0);
+			assert.strictEqual(newBob.followingCount, 1);
+			const newAlice = await Users.findOneByOrFail({ id: alice.id });
+			assert.strictEqual(newAlice.followersCount, 1);
+			assert.strictEqual(newAlice.followingCount, 0);
+			connection.destroy();
 		});
 
 		test('既にフォローしている場合は怒る', async () => {
@@ -341,6 +352,16 @@ describe('Endpoints', () => {
 			}, bob);
 
 			assert.strictEqual(res.status, 200);
+
+			const connection = await initTestDb(true);
+			const Users = connection.getRepository(User);
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.followersCount, 0);
+			assert.strictEqual(newBob.followingCount, 0);
+			const newAlice = await Users.findOneByOrFail({ id: alice.id });
+			assert.strictEqual(newAlice.followersCount, 0);
+			assert.strictEqual(newAlice.followingCount, 0);
+			connection.destroy();
 		});
 
 		test('フォローしていない場合は怒る', async () => {
@@ -382,6 +403,100 @@ describe('Endpoints', () => {
 		});
 	});
 
+	describe('channels/search', () => {
+		test('空白検索で一覧を取得できる', async () => {
+			await api('/channels/create', {
+				name: 'aaa',
+				description: 'bbb',
+			}, bob);
+			await api('/channels/create', {
+				name: 'ccc1',
+				description: 'ddd1',
+			}, bob);
+			await api('/channels/create', {
+				name: 'ccc2',
+				description: 'ddd2',
+			}, bob);
+
+			const res = await api('/channels/search', {
+				query: '',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 3);
+		});
+		test('名前のみの検索で名前を検索できる', async () => {
+			const res = await api('/channels/search', {
+				query: 'aaa',
+				type: 'nameOnly',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 1);
+			assert.strictEqual(res.body[0].name, 'aaa');
+		});
+		test('名前のみの検索で名前を複数検索できる', async () => {
+			const res = await api('/channels/search', {
+				query: 'ccc',
+				type: 'nameOnly',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 2);
+		});
+		test('名前のみの検索で説明は検索できない', async () => {
+			const res = await api('/channels/search', {
+				query: 'bbb',
+				type: 'nameOnly',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 0);
+		});
+		test('名前と説明の検索で名前を検索できる', async () => {
+			const res = await api('/channels/search', {
+				query: 'ccc1',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 1);
+			assert.strictEqual(res.body[0].name, 'ccc1');
+		});
+		test('名前と説明での検索で説明を検索できる', async () => {
+			const res = await api('/channels/search', {
+				query: 'ddd1',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 1);
+			assert.strictEqual(res.body[0].name, 'ccc1');
+		});
+		test('名前と説明の検索で名前を複数検索できる', async () => {
+			const res = await api('/channels/search', {
+				query: 'ccc',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 2);
+		});
+		test('名前と説明での検索で説明を複数検索できる', async () => {
+			const res = await api('/channels/search', {
+				query: 'ddd',
+			}, bob);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(typeof res.body === 'object' && Array.isArray(res.body), true);
+			assert.strictEqual(res.body.length, 2);
+		});
+	});
+
 	describe('drive', () => {
 		test('ドライブ情報を取得できる', async () => {
 			await uploadFile(alice, {
@@ -849,4 +964,85 @@ describe('Endpoints', () => {
 			assert.strictEqual(res.body.error.code, 'URL_PREVIEW_FAILED');
 		});
 	});
+
+	describe('パーソナルメモ機能のテスト', () => {
+		test('他者に関するメモを更新できる', async () => {
+			const memo = '10月まで低浮上とのこと。';
+
+			const res1 = await api('/users/update-memo', {
+				memo,
+				userId: bob.id,
+			}, alice);
+
+			const res2 = await api('/users/show', {
+				userId: bob.id,
+			}, alice);
+			assert.strictEqual(res1.status, 204);
+			assert.strictEqual(res2.body?.memo, memo);
+		});
+
+		test('自分に関するメモを更新できる', async () => {
+			const memo = 'チケットを月末までに買う。';
+
+			const res1 = await api('/users/update-memo', {
+				memo,
+				userId: alice.id,
+			}, alice);
+
+			const res2 = await api('/users/show', {
+				userId: alice.id,
+			}, alice);
+			assert.strictEqual(res1.status, 204);
+			assert.strictEqual(res2.body?.memo, memo);
+		});
+
+		test('メモを削除できる', async () => {
+			const memo = '10月まで低浮上とのこと。';
+
+			await api('/users/update-memo', {
+				memo,
+				userId: bob.id,
+			}, alice);
+
+			await api('/users/update-memo', {
+				memo: '',
+				userId: bob.id,
+			}, alice);
+
+			const res = await api('/users/show', {
+				userId: bob.id,
+			}, alice);
+
+			// memoには常に文字列かnullが入っている(5cac151)
+			assert.strictEqual(res.body.memo, null);
+		});
+
+		test('メモは個人ごとに独立して保存される', async () => {
+			const memoAliceToBob = '10月まで低浮上とのこと。';
+			const memoCarolToBob = '例の件について今度問いただす。';
+
+			await Promise.all([
+				api('/users/update-memo', {
+					memo: memoAliceToBob,
+					userId: bob.id,
+				}, alice),
+				api('/users/update-memo', {
+					memo: memoCarolToBob,
+					userId: bob.id,
+				}, carol),
+			]);
+
+			const [resAlice, resCarol] = await Promise.all([
+				api('/users/show', {
+					userId: bob.id,
+				}, alice),
+				api('/users/show', {
+					userId: bob.id,
+				}, carol),
+			]);
+
+			assert.strictEqual(resAlice.body.memo, memoAliceToBob);
+			assert.strictEqual(resCarol.body.memo, memoCarolToBob);
+		});
+	});
 });
diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7d6c646090bc321bad2f58d4095d498de3f68752
--- /dev/null
+++ b/packages/backend/test/e2e/move.ts
@@ -0,0 +1,456 @@
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import rndstr from 'rndstr';
+import { loadConfig } from '@/config.js';
+import { User, UsersRepository } from '@/models/index.js';
+import { jobQueue } from '@/boot/common.js';
+import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js';
+import type { INestApplicationContext } from '@nestjs/common';
+
+describe('Account Move', () => {
+	let app: INestApplicationContext;
+	let jq: INestApplicationContext;
+	let url: URL;
+
+	let root: any;
+	let alice: any;
+	let bob: any;
+	let carol: any;
+	let dave: any;
+	let eve: any;
+	let frank: any;
+
+	let Users: UsersRepository;
+
+	beforeAll(async () => {
+		app = await startServer();
+		jq = await jobQueue();
+		const config = loadConfig();
+		url = new URL(config.url);
+		const connection = await initTestDb(false);
+		root = await signup({ username: 'root' });
+		alice = await signup({ username: 'alice' });
+		bob = await signup({ username: 'bob' });
+		carol = await signup({ username: 'carol' });
+		dave = await signup({ username: 'dave' });
+		eve = await signup({ username: 'eve' });
+		frank = await signup({ username: 'frank' });
+		Users = connection.getRepository(User);
+	}, 1000 * 60 * 2);
+
+	afterAll(async () => {
+		await Promise.all([app.close(), jq.close()]);
+	});
+
+	describe('Create Alias', () => {
+		afterEach(async () => {
+			await Users.update(bob.id, { alsoKnownAs: null });
+		}, 1000 * 10);
+
+		test('Able to create an alias', async () => {
+			const res = await api('/i/update', {
+				alsoKnownAs: [`@alice@${url.hostname}`],
+			}, bob);
+
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.alsoKnownAs?.length, 1);
+			assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`);
+			assert.strictEqual(res.body.alsoKnownAs?.length, 1);
+			assert.strictEqual(res.body.alsoKnownAs[0], alice.id);
+		});
+
+		test('Able to create a local alias without hostname', async () => {
+			await api('/i/update', {
+				alsoKnownAs: ['@alice'],
+			}, bob);
+
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.alsoKnownAs?.length, 1);
+			assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`);
+		});
+
+		test('Able to create a local alias without @', async () => {
+			await api('/i/update', {
+				alsoKnownAs: ['alice'],
+			}, bob);
+
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.alsoKnownAs?.length, 1);
+			assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`);
+		});
+
+		test('Able to set remote user (but may fail)', async () => {
+			const res = await api('/i/update', {
+				alsoKnownAs: ['@syuilo@example.com'],
+			}, bob);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'NO_SUCH_USER');
+			assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
+		});
+
+		test('Unable to add duplicated aliases to alsoKnownAs', async () => {
+			const res = await api('/i/update', {
+				alsoKnownAs: [`@alice@${url.hostname}`, `@alice@${url.hostname}`],
+			}, bob);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'INVALID_PARAM');
+			assert.strictEqual(res.body.error.id, '3d81ceae-475f-4600-b2a8-2bc116157532');
+		});
+
+		test('Unable to add itself', async () => {
+			const res = await api('/i/update', {
+				alsoKnownAs: [`@bob@${url.hostname}`],
+			}, bob);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'FORBIDDEN_TO_SET_YOURSELF');
+			assert.strictEqual(res.body.error.id, '25c90186-4ab0-49c8-9bba-a1fa6c202ba4');
+		});
+
+		test('Unable to add a nonexisting local account to alsoKnownAs', async () => {
+			const res1 = await api('/i/update', {
+				alsoKnownAs: [`@nonexist@${url.hostname}`],
+			}, bob);
+
+			assert.strictEqual(res1.status, 400);
+			assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER');
+			assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
+
+			const res2 = await api('/i/update', {
+				alsoKnownAs: ['@alice', 'nonexist'],
+			}, bob);
+
+			assert.strictEqual(res2.status, 400);
+			assert.strictEqual(res2.body.error.code, 'NO_SUCH_USER');
+			assert.strictEqual(res2.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
+		});
+
+		test('Able to add two existing local account to alsoKnownAs', async () => {
+			await api('/i/update', {
+				alsoKnownAs: [`@alice@${url.hostname}`, `@carol@${url.hostname}`],
+			}, bob);
+
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.alsoKnownAs?.length, 2);
+			assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`);
+			assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${carol.id}`);
+		});
+
+		test('Able to properly overwrite alsoKnownAs', async () => {
+			await api('/i/update', {
+				alsoKnownAs: [`@alice@${url.hostname}`],
+			}, bob);
+			await api('/i/update', {
+				alsoKnownAs: [`@carol@${url.hostname}`, `@dave@${url.hostname}`],
+			}, bob);
+
+			const newBob = await Users.findOneByOrFail({ id: bob.id });
+			assert.strictEqual(newBob.alsoKnownAs?.length, 2);
+			assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${carol.id}`);
+			assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${dave.id}`);
+		});
+	});
+
+	describe('Local to Local', () => {
+		let antennaId = '';
+
+		beforeAll(async () => {
+			await api('/i/update', {
+				alsoKnownAs: [`@alice@${url.hostname}`],
+			}, root);
+			const listRoot = await api('/users/lists/create', {
+				name: rndstr('0-9a-z', 8),
+			}, root);
+			await api('/users/lists/push', {
+				listId: listRoot.body.id,
+				userId: alice.id,
+			}, root);
+
+			await api('/following/create', {
+				userId: root.id,
+			}, alice);
+			await api('/following/create', {
+				userId: eve.id,
+			}, alice);
+			const antenna = await api('/antennas/create', {
+				name: rndstr('0-9a-z', 8),
+				src: 'home',
+				keywords: [rndstr('0-9a-z', 8)],
+				excludeKeywords: [],
+				users: [],
+				caseSensitive: false,
+				withReplies: false,
+				withFile: false,
+				notify: false,
+			}, alice);
+			antennaId = antenna.body.id;
+
+			await api('/i/update', {
+				alsoKnownAs: [`@alice@${url.hostname}`],
+			}, bob);
+
+			await api('/following/create', {
+				userId: alice.id,
+			}, carol);
+
+			await api('/mute/create', {
+				userId: alice.id,
+			}, dave);
+			await api('/blocking/create', {
+				userId: alice.id,
+			}, dave);
+			await api('/following/create', {
+				userId: eve.id,
+			}, dave);
+
+			await api('/following/create', {
+				userId: dave.id,
+			}, eve);
+			const listEve = await api('/users/lists/create', {
+				name: rndstr('0-9a-z', 8),
+			}, eve);
+			await api('/users/lists/push', {
+				listId: listEve.body.id,
+				userId: bob.id,
+			}, eve);
+
+			await api('/i/update', {
+				isLocked: true,
+			}, frank);
+			await api('/following/create', {
+				userId: frank.id,
+			}, alice);
+			await api('/following/requests/accept', {
+				userId: alice.id,
+			}, frank);
+		}, 1000 * 10);
+
+		test('Prohibit the root account from moving', async () => {
+			const res = await api('/i/move', {
+				moveToAccount: `@bob@${url.hostname}`,
+			}, root);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'NOT_ROOT_FORBIDDEN');
+			assert.strictEqual(res.body.error.id, '4362e8dc-731f-4ad8-a694-be2a88922a24');
+		});
+
+		test('Unable to move to a nonexisting local account', async () => {
+			const res = await api('/i/move', {
+				moveToAccount: `@nonexist@${url.hostname}`,
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'NO_SUCH_USER');
+			assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
+		});
+
+		test('Unable to move if alsoKnownAs is invalid', async () => {
+			const res = await api('/i/move', {
+				moveToAccount: `@carol@${url.hostname}`,
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS');
+			assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
+		});
+
+		test('Relationships have been properly migrated', async () => {
+			const move = await api('/i/move', {
+				moveToAccount: `@bob@${url.hostname}`,
+			}, alice);
+
+			assert.strictEqual(move.status, 200);
+
+			await sleep(1000 * 3); // wait for jobs to finish
+
+			// Unfollow delayed?
+			const aliceFollowings = await api('/users/following', {
+				userId: alice.id,
+			}, alice);
+			assert.strictEqual(aliceFollowings.status, 200);
+			assert.strictEqual(aliceFollowings.body.length, 3);
+
+			const carolFollowings = await api('/users/following', {
+				userId: carol.id,
+			}, carol);
+			assert.strictEqual(carolFollowings.status, 200);
+			assert.strictEqual(carolFollowings.body.length, 2);
+			assert.strictEqual(carolFollowings.body[0].followeeId, bob.id);
+			assert.strictEqual(carolFollowings.body[1].followeeId, alice.id);
+
+			const blockings = await api('/blocking/list', {}, dave);
+			assert.strictEqual(blockings.status, 200);
+			assert.strictEqual(blockings.body.length, 2);
+			assert.strictEqual(blockings.body[0].blockeeId, bob.id);
+			assert.strictEqual(blockings.body[1].blockeeId, alice.id);
+
+			const mutings = await api('/mute/list', {}, dave);
+			assert.strictEqual(mutings.status, 200);
+			assert.strictEqual(mutings.body.length, 2);
+			assert.strictEqual(mutings.body[0].muteeId, bob.id);
+			assert.strictEqual(mutings.body[1].muteeId, alice.id);
+
+			const rootLists = await api('/users/lists/list', {}, root);
+			assert.strictEqual(rootLists.status, 200);
+			assert.strictEqual(rootLists.body[0].userIds.length, 2);
+			assert.ok(rootLists.body[0].userIds.find((id: string) => id === bob.id));
+			assert.ok(rootLists.body[0].userIds.find((id: string) => id === alice.id));
+
+			const eveLists = await api('/users/lists/list', {}, eve);
+			assert.strictEqual(eveLists.status, 200);
+			assert.strictEqual(eveLists.body[0].userIds.length, 1);
+			assert.ok(eveLists.body[0].userIds.find((id: string) => id === bob.id));
+		});
+
+		test('A locked account automatically accept the follow request if it had already accepted the old account.', async () => {
+			await successfulApiCall({
+				endpoint: '/following/create',
+				parameters: {
+					userId: frank.id,
+				},
+				user: bob,
+			});
+			const followers = await api('/users/followers', {
+				userId: frank.id,
+			}, frank);
+
+			assert.strictEqual(followers.status, 200);
+			assert.strictEqual(followers.body.length, 2);
+			assert.strictEqual(followers.body[0].followerId, bob.id);
+		});
+
+		test('Unfollowed after 10 sec (24 hours in production).', async () => {
+			await sleep(1000 * 8);
+
+			const following = await api('/users/following', {
+				userId: alice.id,
+			}, alice);
+
+			assert.strictEqual(following.status, 200);
+			assert.strictEqual(following.body.length, 0);
+		});
+
+		test('Unable to move if the destination account has already moved.', async () => {
+			const res = await api('/i/move', {
+				moveToAccount: `@alice@${url.hostname}`,
+			}, bob);
+
+			assert.strictEqual(res.status, 400);
+			assert.strictEqual(res.body.error.code, 'DESTINATION_ACCOUNT_FORBIDS');
+			assert.strictEqual(res.body.error.id, 'b5c90186-4ab0-49c8-9bba-a1f766282ba4');
+		});
+
+		test('Follow and follower counts are properly adjusted', async () => {
+			await api('/following/create', {
+				userId: alice.id,
+			}, eve);
+			const newAlice = await Users.findOneByOrFail({ id: alice.id });
+			const newCarol = await Users.findOneByOrFail({ id: carol.id });
+			let newEve = await Users.findOneByOrFail({ id: eve.id });
+			assert.strictEqual(newAlice.movedToUri, `${url.origin}/users/${bob.id}`);
+			assert.strictEqual(newAlice.followingCount, 0);
+			assert.strictEqual(newAlice.followersCount, 0);
+			assert.strictEqual(newCarol.followingCount, 1);
+			assert.strictEqual(newEve.followingCount, 1);
+			assert.strictEqual(newEve.followersCount, 1);
+
+			await api('/following/delete', {
+				userId: alice.id,
+			}, eve);
+			newEve = await Users.findOneByOrFail({ id: eve.id });
+			assert.strictEqual(newEve.followingCount, 1);
+			assert.strictEqual(newEve.followersCount, 1);
+		});
+
+		test.each([
+			'/antennas/create',
+			'/channels/create',
+			'/channels/favorite',
+			'/channels/follow',
+			'/channels/unfavorite',
+			'/channels/unfollow',
+			'/clips/add-note',
+			'/clips/create',
+			'/clips/favorite',
+			'/clips/remove-note',
+			'/clips/unfavorite',
+			'/clips/update',
+			'/drive/files/upload-from-url',
+			'/flash/create',
+			'/flash/like',
+			'/flash/unlike',
+			'/flash/update',
+			'/following/create',
+			'/gallery/posts/create',
+			'/gallery/posts/like',
+			'/gallery/posts/unlike',
+			'/gallery/posts/update',
+			'/i/claim-achievement',
+			'/i/move',
+			'/i/import-blocking',
+			'/i/import-following',
+			'/i/import-muting',
+			'/i/import-user-lists',
+			'/i/pin',
+			'/mute/create',
+			'/notes/create',
+			'/notes/favorites/create',
+			'/notes/polls/vote',
+			'/notes/reactions/create',
+			'/pages/create',
+			'/pages/like',
+			'/pages/unlike',
+			'/pages/update',
+			'/renote-mute/create',
+			'/users/lists/create',
+			'/users/lists/pull',
+			'/users/lists/push',
+		])('Prohibit access after moving: %s', async (endpoint) => {
+			const res = await api(endpoint, {}, alice);
+			assert.strictEqual(res.status, 403);
+			assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
+			assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
+		});
+
+		test('Prohibit access after moving: /antennas/update', async () => {
+			const res = await api('/antennas/update', {
+				antennaId,
+				name: rndstr('0-9a-z', 8),
+				src: 'users',
+				keywords: [rndstr('0-9a-z', 8)],
+				excludeKeywords: [],
+				users: [eve.id],
+				caseSensitive: false,
+				withReplies: false,
+				withFile: false,
+				notify: false,
+			}, alice);
+
+			assert.strictEqual(res.status, 403);
+			assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
+			assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
+		});
+
+		test('Prohibit access after moving: /drive/files/create', async () => {
+			const res = await uploadFile(alice);
+
+			assert.strictEqual(res.status, 403);
+			assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
+			assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
+		});
+
+		test('Prohibit updating alsoKnownAs after moving', async () => {
+			const res = await api('/i/update', {
+				alsoKnownAs: [`@eve@${url.hostname}`],
+			}, alice);
+
+			assert.strictEqual(res.status, 403);
+			assert.strictEqual(res.body.error.code, 'YOUR_ACCOUNT_MOVED');
+			assert.strictEqual(res.body.error.id, '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31');
+		});
+	});
+});
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index e87045a8cf6180ebce8cb7158e1195de6c2af48a..9c851a5dd671a74ffa80e7155460c6f13bf29dd9 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -352,6 +352,72 @@ describe('Note', () => {
 			assert.strictEqual(myNote.renote.reply.files.length, 1);
 			assert.strictEqual(myNote.renote.reply.files[0].id, file.body.id);
 		});
+
+		test('NSFWが強制されている場合変更できない', async () => {
+			const file = await uploadFile(alice);
+
+			const res = await api('admin/roles/create', {
+				name: 'test',
+				description: '',
+				color: null,
+				iconUrl: null,
+				displayOrder: 0,
+				target: 'manual',
+				condFormula: {},
+				isAdministrator: false,
+				isModerator: false,
+				isPublic: false,
+				isExplorable: false,
+				asBadge: false,
+				canEditMembersByModerator: false,
+				policies: {
+					alwaysMarkNsfw: {
+						useDefault: false,
+						priority: 0,
+						value: true,
+					},
+				},
+			}, alice);
+			
+			assert.strictEqual(res.status, 200);
+
+			const assign = await api('admin/roles/assign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			}, alice);
+
+			assert.strictEqual(assign.status, 204);
+			assert.strictEqual(file.body.isSensitive, false);
+
+			const nsfwfile = await uploadFile(alice);
+
+			assert.strictEqual(nsfwfile.status, 200);
+			assert.strictEqual(nsfwfile.body.isSensitive, true);
+
+			const liftnsfw = await api('drive/files/update', {
+				fileId: nsfwfile.body.id,
+				isSensitive: false,
+			}, alice);
+
+			assert.strictEqual(liftnsfw.status, 400);
+			assert.strictEqual(liftnsfw.body.error.code, 'RESTRICTED_BY_ROLE');
+
+			const oldaddnsfw = await api('drive/files/update', {
+				fileId: file.body.id,
+				isSensitive: true,
+			}, alice);
+
+			assert.strictEqual(oldaddnsfw.status, 200);
+
+			await api('admin/roles/unassign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			});
+
+			await api('admin/roles/delete', {
+				roleId: res.body.id,
+			}, alice);
+		});
 	});
 
 	describe('notes/create', () => {
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index bc3455e3463ae012b3ad5af5faaa8d301c2d1f9c..51537dda16b8e7a26726944ecc284e95a872277c 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -31,7 +31,7 @@ describe('ユーザー', () => {
 			}, {});
 	};
 
-	// FIXME: 足りないキーがたくさんある
+	// BUG misskey-jsとjson-schemaと実際に返ってくるデータが全部違う
 	type UserLite = misskey.entities.UserLite & {
 		badgeRoles: any[],
 	};
@@ -51,10 +51,11 @@ describe('ユーザー', () => {
 	
 	type User = MeDetailed & { token: string };	
 
-	const show = async (id: string, me = alice): Promise<MeDetailed | UserDetailedNotMe> => {
+	const show = async (id: string, me = root): Promise<MeDetailed | UserDetailedNotMe> => {
 		return successfulApiCall({ endpoint: 'users/show', parameters: { userId: id }, user: me }) as any;
 	};
 
+	// UserLiteのキーが過不足なく入っている?
 	const userLite = (user: User): Partial<UserLite> => {
 		return stripUndefined({
 			id: user.id,
@@ -76,12 +77,13 @@ describe('ユーザー', () => {
 		});
 	};
 
+	// UserDetailedNotMeのキーが過不足なく入っている?
 	const userDetailedNotMe = (user: User): Partial<UserDetailedNotMe> => {
 		return stripUndefined({
 			...userLite(user),
 			url: user.url,
 			uri: user.uri,
-			movedToUri: user.movedToUri,
+			movedTo: user.movedTo,
 			alsoKnownAs: user.alsoKnownAs,
 			createdAt: user.createdAt,
 			updatedAt: user.updatedAt,
@@ -109,9 +111,11 @@ describe('ユーザー', () => {
 			usePasswordLessLogin: user.usePasswordLessLogin,
 			securityKeys: user.securityKeys,
 			roles: user.roles,
+			memo: user.memo,
 		});
 	};
 
+	// Relations関連のキーが過不足なく入っている?
 	const userDetailedNotMeWithRelations = (user: User): Partial<UserDetailedNotMe> => {
 		return stripUndefined({
 			...userDetailedNotMe(user),
@@ -126,6 +130,7 @@ describe('ユーザー', () => {
 		});
 	};
 
+	// MeDetailedのキーが過不足なく入っている?
 	const meDetailed = (user: User, security = false): Partial<MeDetailed> => {
 		return stripUndefined({
 			...userDetailedNotMe(user),
@@ -216,8 +221,8 @@ describe('ユーザー', () => {
 	}, 1000 * 60 * 2);
 
 	beforeAll(async () => {
-		root = await signup({ username: 'alice' });
-		alice = root;
+		root = await signup({ username: 'root' });
+		alice = await signup({ username: 'alice' });
 		aliceNote = await post(alice, { text: 'test' }) as any; 
 		alicePage = await page(alice);
 		aliceList = (await api('users/list/create', { name: 'aliceList' }, alice)).body;
@@ -343,7 +348,7 @@ describe('ユーザー', () => {
 		// UserDetailedNotMeOnly
 		assert.strictEqual(response.url, null);
 		assert.strictEqual(response.uri, null);
-		assert.strictEqual(response.movedToUri, null);
+		assert.strictEqual(response.movedTo, null);
 		assert.strictEqual(response.alsoKnownAs, null);
 		assert.strictEqual(response.createdAt, new Date(response.createdAt).toISOString());
 		assert.strictEqual(response.updatedAt, null);
@@ -371,6 +376,7 @@ describe('ユーザー', () => {
 		assert.strictEqual(response.usePasswordLessLogin, false);
 		assert.strictEqual(response.securityKeys, false);
 		assert.deepStrictEqual(response.roles, []);
+		assert.strictEqual(response.memo, null);
 		
 		// MeDetailedOnly
 		assert.strictEqual(response.avatarId, null);
@@ -410,7 +416,7 @@ describe('ユーザー', () => {
 	//#endregion
 	//#region 自分の情報(i)
 
-	test('を読み取ることができる。(自分)', async () => {
+	test('を読み取ることができること(自分)、キーが過不足なく入っていること。', async () => {
 		const response = await successfulApiCall({
 			endpoint: 'i',
 			parameters: {},
@@ -502,7 +508,6 @@ describe('ユーザー', () => {
 		};
 		assert.deepStrictEqual(response, expected, inspect(parameters));
 
-		if (1) return; // BUG 521eb95 以降アバターのリセットができない。
 		const parameters2 = { avatarId: null };
 		const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice });
 		const expected2 = { 
@@ -528,7 +533,6 @@ describe('ユーザー', () => {
 		};
 		assert.deepStrictEqual(response, expected, inspect(parameters));
 
-		if (1) return; // BUG 521eb95 以降バナーのリセットができない。
 		const parameters2 = { bannerId: null };
 		const response2 = await successfulApiCall({ endpoint: 'i/update', parameters: parameters2, user: alice });
 		const expected2 = { 
@@ -554,6 +558,21 @@ describe('ユーザー', () => {
 		assert.deepStrictEqual(response2, expected2);
 	});
 
+	//#endregion
+	//#region メモの更新(users/update-memo)
+
+	test.each([
+		{ label: '最大長', memo: 'x'.repeat(2048) },
+		{ label: '空文字', memo: '', expects: null },
+		{ label: 'null', memo: null },
+	])('を書き換えることができる(メモを$labelに)', async ({ memo, expects }) => {
+		const expected = { ...await show(bob.id, alice), memo: expects === undefined ? memo : expects };
+		const parameters = { userId: bob.id, memo };
+		await successfulApiCall({ endpoint: 'users/update-memo', parameters, user: alice });
+		const response = await show(bob.id, alice);
+		assert.deepStrictEqual(response, expected);
+	});
+
 	//#endregion
 	//#region ユーザー(users)
 
@@ -569,7 +588,7 @@ describe('ユーザー', () => {
 		const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice });
 
 		// 結果の並びを事前にアサートするのは困難なので返ってきたidに対応するユーザーが返っており、ソート順が正しいことだけを検証する
-		const users = await Promise.all(response.map(u => show(u.id)));
+		const users = await Promise.all(response.map(u => show(u.id, alice)));
 		const expected = users.sort((x, y) => {
 			const index = (selector(x) < selector(y)) ? -1 : (selector(x) > selector(y)) ? 1 : 0;
 			return index * (parameters.sort?.startsWith('+') ? -1 : 1);
@@ -583,13 +602,13 @@ describe('ユーザー', () => {
 		{ label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice, excluded: true },
 		{ label: '承認制ユーザーが含まれる', user: (): User => userLocking },
 		{ label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced },
-		{ label: 'サスペンドユーザーが含まれる', user: (): User => userSuspended },
+		{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true },
 		{ label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf },
 		{ label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin },
 	] as const)('をリスト形式で取得することができ、結果に$label', async ({ user, excluded }) => {
 		const parameters = { limit: 100 };
 		const response = await successfulApiCall({ endpoint: 'users', parameters, user: alice });
-		const expected = (excluded ?? false) ? [] : [await show(user().id)];
+		const expected = (excluded ?? false) ? [] : [await show(user().id, alice)];
 		assert.deepStrictEqual(response.filter((u) => u.id === user().id), expected);
 	});
 	test.todo('をリスト形式で取得することができる(リモート, hostname指定)');
@@ -616,7 +635,7 @@ describe('ユーザー', () => {
 		{ label: 'Moderatorになっている', user: (): User => userModerator, me: (): User => userModerator, selector: (user: User): unknown => user.isModerator },
 		{ label: '自分以外から見たときはModeratorか判定できない', user: (): User => userModerator, selector: (user: User): unknown => user.isModerator, expected: (): undefined => undefined },
 		{ label: 'サイレンスになっている', user: (): User => userSilenced, selector: (user: User): unknown => user.isSilenced },
-		{ label: 'サスペンドになっている', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended },
+		//{ label: 'サスペンドになっている', user: (): User => userSuspended, selector: (user: User): unknown => user.isSuspended },
 		{ label: '削除済みになっている', user: (): User => userDeletedBySelf, me: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted },
 		{ label: '自分以外から見たときは削除済みか判定できない', user: (): User => userDeletedBySelf, selector: (user: User): unknown => user.isDeleted, expected: (): undefined => undefined },
 		{ label: '削除済み(byAdmin)になっている', user: (): User => userDeletedByAdmin, me: (): User => userDeletedByAdmin, selector: (user: User): unknown => user.isDeleted },
@@ -698,13 +717,13 @@ describe('ユーザー', () => {
 	test('を検索することができる', async () => {
 		const parameters = { query: 'carol', limit: 10 };
 		const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice });
-		const expected = [await show(carol.id)];
+		const expected = [await show(carol.id, alice)];
 		assert.deepStrictEqual(response, expected);
 	});
 	test('を検索することができる(UserLite)', async () => {
 		const parameters = { query: 'carol', detail: false, limit: 10 };
 		const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice });
-		const expected = [userLite(await show(carol.id))];
+		const expected = [userLite(await show(carol.id, alice))];
 		assert.deepStrictEqual(response, expected);
 	});
 	test.each([
@@ -720,7 +739,7 @@ describe('ユーザー', () => {
 	] as const)('を検索することができ、結果に$labelが含まれる', async ({ user, excluded }) => {
 		const parameters = { query: user().username, limit: 1 };
 		const response = await successfulApiCall({ endpoint: 'users/search', parameters, user: alice });
-		const expected = (excluded ?? false) ? [] : [await show(user().id)];
+		const expected = (excluded ?? false) ? [] : [await show(user().id, alice)];
 		assert.deepStrictEqual(response, expected);
 	});
 	test.todo('を検索することができる(リモート)');
@@ -741,7 +760,7 @@ describe('ユーザー', () => {
 		{ label: 'ローカル', parameters: { host: '.', limit: 1 }, user: (): User[] => [userFollowedByAlice] },
 	])('をID&ホスト指定で検索できる($label)', async ({ parameters, user }) => {
 		const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice });
-		const expected = await Promise.all(user().map(u => show(u.id)));
+		const expected = await Promise.all(user().map(u => show(u.id, alice)));
 		assert.deepStrictEqual(response, expected);
 	});
 	test.each([
@@ -757,7 +776,7 @@ describe('ユーザー', () => {
 	] as const)('をID&ホスト指定で検索でき、結果に$label', async ({ user, excluded }) => {
 		const parameters = { username: user().username };
 		const response = await successfulApiCall({ endpoint: 'users/search-by-username-and-host', parameters, user: alice });
-		const expected = (excluded ?? false) ? [] : [await show(user().id)];
+		const expected = (excluded ?? false) ? [] : [await show(user().id, alice)];
 		assert.deepStrictEqual(response, expected);
 	});
 	test.todo('をID&ホスト指定で検索できる(リモート)');
@@ -769,7 +788,7 @@ describe('ユーザー', () => {
 		const parameters = { userId: alice.id, limit: 5 };
 		const response = await successfulApiCall({ endpoint: 'users/get-frequently-replied-users', parameters, user: alice });
 		const expected = await Promise.all(usersReplying.slice(0, parameters.limit).map(async (s, i) => ({ 
-			user: await show(s.id),
+			user: await show(s.id, alice),
 			weight: (usersReplying.length - i) / usersReplying.length,
 		})));
 		assert.deepStrictEqual(response, expected);
@@ -781,7 +800,7 @@ describe('ユーザー', () => {
 		{ label: 'ブロックしてきているユーザーが含まれない', user: (): User => userBlockingAlice, excluded: true },
 		{ label: '承認制ユーザーが含まれる', user: (): User => userLocking },
 		{ label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced },
-		{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended },
+		//{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true },
 		{ label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf },
 		{ label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin },
 	] as const)('がよくリプライをするユーザーのリストを取得でき、結果に$label', async ({ user, excluded }) => {
@@ -789,7 +808,7 @@ describe('ユーザー', () => {
 		await post(alice, { text: `@${user().username} test`, replyId: replyTo.id });
 		const parameters = { userId: alice.id, limit: 100 };
 		const response = await successfulApiCall({ endpoint: 'users/get-frequently-replied-users', parameters, user: alice });
-		const expected = (excluded ?? false) ? [] : [await show(user().id)];
+		const expected = (excluded ?? false) ? [] : [await show(user().id, alice)];
 		assert.deepStrictEqual(response.map(s => s.user).filter((u) => u.id === user().id), expected);
 	});
 
@@ -808,7 +827,7 @@ describe('ユーザー', () => {
 		await successfulApiCall({ endpoint: 'i/update', parameters: { description: `#${hashtag}` }, user: alice });
 		const parameters = { tag: hashtag, limit: 5, ...sort };
 		const response = await successfulApiCall({ endpoint: 'hashtags/users', parameters, user: alice });
-		const users = await Promise.all(response.map(u => show(u.id)));
+		const users = await Promise.all(response.map(u => show(u.id, alice)));
 		const expected = users.sort((x, y) => {
 			const index = (selector(x) < selector(y)) ? -1 : (selector(x) > selector(y)) ? 1 : 0;
 			return index * (parameters.sort.startsWith('+') ? -1 : 1);
@@ -822,10 +841,10 @@ describe('ユーザー', () => {
 		{ label: 'ブロックしてきているユーザーが含まれる', user: (): User => userBlockingAlice },
 		{ label: '承認制ユーザーが含まれる', user: (): User => userLocking },
 		{ label: 'サイレンスユーザーが含まれる', user: (): User => userSilenced },
-		{ label: 'サスペンドユーザーが含まれる', user: (): User => userSuspended },
+		{ label: 'サスペンドユーザーが含まれない', user: (): User => userSuspended, excluded: true },
 		{ label: '削除済ユーザーが含まれる', user: (): User => userDeletedBySelf },
 		{ label: '削除済(byAdmin)ユーザーが含まれる', user: (): User => userDeletedByAdmin },
-	] as const)('をハッシュタグ指定で取得することができ、結果に$label', async ({ user }) => {
+	] as const)('をハッシュタグ指定で取得することができ、結果に$label', async ({ user, excluded }) => {
 		const hashtag = `user_test${user().username}`;
 		if (user() !== userSuspended) {
 			// サスペンドユーザーはupdateできない。
@@ -833,7 +852,7 @@ describe('ユーザー', () => {
 		}
 		const parameters = { tag: hashtag, limit: 100, sort: '-follower' } as const;
 		const response = await successfulApiCall({ endpoint: 'hashtags/users', parameters, user: alice });
-		const expected = [await show(user().id)];
+		const expected = (excluded ?? false) ? [] : [await show(user().id, alice)];
 		assert.deepStrictEqual(response, expected);
 	});
 	test.todo('をハッシュタグ指定で取得することができる(リモート)');
@@ -856,7 +875,7 @@ describe('ユーザー', () => {
 		await successfulApiCall({ endpoint: 'admin/update-meta', parameters: { pinnedUsers: [bob.username, `@${carol.username}`] }, user: root });
 		const parameters = {} as const;
 		const response = await successfulApiCall({ endpoint: 'pinned-users', parameters, user: alice });
-		const expected = await Promise.all([bob, carol].map(u => show(u.id)));
+		const expected = await Promise.all([bob, carol].map(u => show(u.id, alice)));
 		assert.deepStrictEqual(response, expected);
 	});
 
diff --git a/packages/backend/test/resources/kick_gaba7.aac b/packages/backend/test/resources/kick_gaba7.aac
new file mode 100644
index 0000000000000000000000000000000000000000..4644542f9664182a78bc1d8b6116c5706200ced5
Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.aac differ
diff --git a/packages/backend/test/resources/kick_gaba7.flac b/packages/backend/test/resources/kick_gaba7.flac
new file mode 100644
index 0000000000000000000000000000000000000000..75128120182a70cec0fcf52efc3df7b77972c38b
Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.flac differ
diff --git a/packages/backend/test/resources/kick_gaba7.mp3 b/packages/backend/test/resources/kick_gaba7.mp3
new file mode 100644
index 0000000000000000000000000000000000000000..6ba317deb1a04eb62384b2cf0c79c7d901f2bbf1
Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.mp3 differ
diff --git a/packages/backend/test/resources/kick_gaba7.wav b/packages/backend/test/resources/kick_gaba7.wav
new file mode 100644
index 0000000000000000000000000000000000000000..2cd280148e125c77596b97836503b1777f793564
Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.wav differ
diff --git a/packages/backend/test/resources/kick_gaba7.webm b/packages/backend/test/resources/kick_gaba7.webm
new file mode 100644
index 0000000000000000000000000000000000000000..82c5349cd4a20dd3d890d57ab61f0354bd846e8a
Binary files /dev/null and b/packages/backend/test/resources/kick_gaba7.webm differ
diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts
index d05833560d60185e99b9b2ebf5e32f0fc7fca4a3..f378184c74f3bf1a7333f2a44194e7af26848945 100644
--- a/packages/backend/test/unit/FileInfoService.ts
+++ b/packages/backend/test/unit/FileInfoService.ts
@@ -7,10 +7,10 @@ import { ModuleMocker } from 'jest-mock';
 import { Test } from '@nestjs/testing';
 import { GlobalModule } from '@/GlobalModule.js';
 import { FileInfoService } from '@/core/FileInfoService.js';
-import { DI } from '@/di-symbols.js';
+//import { DI } from '@/di-symbols.js';
 import { AiService } from '@/core/AiService.js';
 import type { TestingModule } from '@nestjs/testing';
-import type { jest } from '@jest/globals';
+import { describe, beforeAll, afterAll, test } from '@jest/globals';
 import type { MockFunctionMetadata } from 'jest-mock';
 
 const _filename = fileURLToPath(import.meta.url);
@@ -74,164 +74,271 @@ describe('FileInfoService', () => {
 		});
 	});
 
-	test('Generic JPEG', async () => {
-		const path = `${resources}/Lenna.jpg`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 25360,
-			md5: '091b3f259662aa31e2ffef4519951168',
-			type: {
-				mime: 'image/jpeg',
-				ext: 'jpg',
-			},
-			width: 512,
-			height: 512,
-			orientation: undefined,
+	describe('IMAGE', () => {
+		test('Generic JPEG', async () => {
+			const path = `${resources}/Lenna.jpg`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 25360,
+				md5: '091b3f259662aa31e2ffef4519951168',
+				type: {
+					mime: 'image/jpeg',
+					ext: 'jpg',
+				},
+				width: 512,
+				height: 512,
+				orientation: undefined,
+			});
 		});
-	});
-
-	test('Generic APNG', async () => {
-		const path = `${resources}/anime.png`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 1868,
-			md5: '08189c607bea3b952704676bb3c979e0',
-			type: {
-				mime: 'image/apng',
-				ext: 'apng',
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
+	
+		test('Generic APNG', async () => {
+			const path = `${resources}/anime.png`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 1868,
+				md5: '08189c607bea3b952704676bb3c979e0',
+				type: {
+					mime: 'image/apng',
+					ext: 'apng',
+				},
+				width: 256,
+				height: 256,
+				orientation: undefined,
+			});
 		});
-	});
-
-	test('Generic AGIF', async () => {
-		const path = `${resources}/anime.gif`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 2248,
-			md5: '32c47a11555675d9267aee1a86571e7e',
-			type: {
-				mime: 'image/gif',
-				ext: 'gif',
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
+	
+		test('Generic AGIF', async () => {
+			const path = `${resources}/anime.gif`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 2248,
+				md5: '32c47a11555675d9267aee1a86571e7e',
+				type: {
+					mime: 'image/gif',
+					ext: 'gif',
+				},
+				width: 256,
+				height: 256,
+				orientation: undefined,
+			});
 		});
-	});
-
-	test('PNG with alpha', async () => {
-		const path = `${resources}/with-alpha.png`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 3772,
-			md5: 'f73535c3e1e27508885b69b10cf6e991',
-			type: {
-				mime: 'image/png',
-				ext: 'png',
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
+	
+		test('PNG with alpha', async () => {
+			const path = `${resources}/with-alpha.png`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 3772,
+				md5: 'f73535c3e1e27508885b69b10cf6e991',
+				type: {
+					mime: 'image/png',
+					ext: 'png',
+				},
+				width: 256,
+				height: 256,
+				orientation: undefined,
+			});
 		});
-	});
-
-	test('Generic SVG', async () => {
-		const path = `${resources}/image.svg`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 505,
-			md5: 'b6f52b4b021e7b92cdd04509c7267965',
-			type: {
-				mime: 'image/svg+xml',
-				ext: 'svg',
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
+	
+		test('Generic SVG', async () => {
+			const path = `${resources}/image.svg`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 505,
+				md5: 'b6f52b4b021e7b92cdd04509c7267965',
+				type: {
+					mime: 'image/svg+xml',
+					ext: 'svg',
+				},
+				width: 256,
+				height: 256,
+				orientation: undefined,
+			});
 		});
-	});
-
-	test('SVG with XML definition', async () => {
-		// https://github.com/misskey-dev/misskey/issues/4413
-		const path = `${resources}/with-xml-def.svg`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 544,
-			md5: '4b7a346cde9ccbeb267e812567e33397',
-			type: {
-				mime: 'image/svg+xml',
-				ext: 'svg',
-			},
-			width: 256,
-			height: 256,
-			orientation: undefined,
+	
+		test('SVG with XML definition', async () => {
+			// https://github.com/misskey-dev/misskey/issues/4413
+			const path = `${resources}/with-xml-def.svg`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 544,
+				md5: '4b7a346cde9ccbeb267e812567e33397',
+				type: {
+					mime: 'image/svg+xml',
+					ext: 'svg',
+				},
+				width: 256,
+				height: 256,
+				orientation: undefined,
+			});
 		});
-	});
-
-	test('Dimension limit', async () => {
-		const path = `${resources}/25000x25000.png`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 75933,
-			md5: '268c5dde99e17cf8fe09f1ab3f97df56',
-			type: {
-				mime: 'application/octet-stream',	// do not treat as image
-				ext: null,
-			},
-			width: 25000,
-			height: 25000,
-			orientation: undefined,
+	
+		test('Dimension limit', async () => {
+			const path = `${resources}/25000x25000.png`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 75933,
+				md5: '268c5dde99e17cf8fe09f1ab3f97df56',
+				type: {
+					mime: 'application/octet-stream',	// do not treat as image
+					ext: null,
+				},
+				width: 25000,
+				height: 25000,
+				orientation: undefined,
+			});
+		});
+	
+		test('Rotate JPEG', async () => {
+			const path = `${resources}/rotate.jpg`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			assert.deepStrictEqual(info, {
+				size: 12624,
+				md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
+				type: {
+					mime: 'image/jpeg',
+					ext: 'jpg',
+				},
+				width: 512,
+				height: 256,
+				orientation: 8,
+			});
 		});
 	});
 
-	test('Rotate JPEG', async () => {
-		const path = `${resources}/rotate.jpg`;
-		const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
-		delete info.warnings;
-		delete info.blurhash;
-		delete info.sensitive;
-		delete info.porn;
-		assert.deepStrictEqual(info, {
-			size: 12624,
-			md5: '68d5b2d8d1d1acbbce99203e3ec3857e',
-			type: {
-				mime: 'image/jpeg',
-				ext: 'jpg',
-			},
-			width: 512,
-			height: 256,
-			orientation: 8,
+	describe('AUDIO', () => {
+		test('MP3', async () => {
+			const path = `${resources}/kick_gaba7.mp3`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			delete info.width;
+			delete info.height;
+			delete info.orientation;
+			assert.deepStrictEqual(info, {
+				size: 19853,
+				md5: '4f557df8548bc3cecc794c652f690446',
+				type: {
+					mime: 'audio/mpeg',
+					ext: 'mp3',
+				},
+			});
+		});
+	
+		test('WAV', async () => {
+			const path = `${resources}/kick_gaba7.wav`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			delete info.width;
+			delete info.height;
+			delete info.orientation;
+			assert.deepStrictEqual(info, {
+				size: 87630,
+				md5: '8bc9bb4fe5e77bb1871448209be635c1',
+				type: {
+					mime: 'audio/wav',
+					ext: 'wav',
+				},
+			});
+		});
+	
+		test('AAC', async () => {
+			const path = `${resources}/kick_gaba7.aac`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			delete info.width;
+			delete info.height;
+			delete info.orientation;
+			assert.deepStrictEqual(info, {
+				size: 7291,
+				md5: '2789323f05e3392b648066f50be6a2a6',
+				type: {
+					mime: 'audio/aac',
+					ext: 'aac',
+				},
+			});
+		});
+	
+		test('FLAC', async () => {
+			const path = `${resources}/kick_gaba7.flac`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			delete info.width;
+			delete info.height;
+			delete info.orientation;
+			assert.deepStrictEqual(info, {
+				size: 108793,
+				md5: 'bc0f3adfe0e1ca99ae6c7528c46b3173',
+				type: {
+					mime: 'audio/flac',
+					ext: 'flac',
+				},
+			});
+		});
+	
+		/*
+		 * video/webmとして検出されてしまう
+		test('WEBM AUDIO', async () => {
+			const path = `${resources}/kick_gaba7.webm`;
+			const info = await fileInfoService.getFileInfo(path, { skipSensitiveDetection: true }) as any;
+			delete info.warnings;
+			delete info.blurhash;
+			delete info.sensitive;
+			delete info.porn;
+			delete info.width;
+			delete info.height;
+			delete info.orientation;
+			assert.deepStrictEqual(info, {
+				size: 8879,
+				md5: '3350083dec312419cfdc06c16413aca7',
+				type: {
+					mime: 'audio/webm',
+					ext: 'webm',
+				},
+			});
 		});
+		 */
 	});
 });
diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts
index 529e923b2c038cd5bf1822ffb1a7ea26a26c1899..c2280142a6d41abc7736f5383d464b198de4bc4f 100644
--- a/packages/backend/test/unit/RelayService.ts
+++ b/packages/backend/test/unit/RelayService.ts
@@ -7,6 +7,7 @@ import { GlobalModule } from '@/GlobalModule.js';
 import { RelayService } from '@/core/RelayService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { CreateSystemUserService } from '@/core/CreateSystemUserService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { QueueService } from '@/core/QueueService.js';
 import { IdService } from '@/core/IdService.js';
 import type { RelaysRepository } from '@/models/index.js';
@@ -21,6 +22,7 @@ describe('RelayService', () => {
 	let relayService: RelayService;
 	let queueService: jest.Mocked<QueueService>;
 	let relaysRepository: RelaysRepository;
+	let userEntityService: UserEntityService;
 
 	beforeAll(async () => {
 		app = await Test.createTestingModule({
@@ -32,6 +34,7 @@ describe('RelayService', () => {
 				CreateSystemUserService,
 				ApRendererService,
 				RelayService,
+				UserEntityService,
 			],
 		})
 			.useMocker((token) => {
@@ -51,6 +54,7 @@ describe('RelayService', () => {
 		relayService = app.get<RelayService>(RelayService);
 		queueService = app.get<QueueService>(QueueService) as jest.Mocked<QueueService>;
 		relaysRepository = app.get<RelaysRepository>(DI.relaysRepository);
+		userEntityService = app.get<UserEntityService>(UserEntityService);
 	});
 
 	afterAll(async () => {
@@ -63,7 +67,7 @@ describe('RelayService', () => {
 		expect(result.inbox).toBe('https://example.com');
 		expect(result.status).toBe('requesting');
 		expect(queueService.deliver).toHaveBeenCalled();
-		expect(queueService.deliver.mock.lastCall![1].type).toBe('Follow');
+		expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Follow');
 		expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com');
 		//expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor');
 	});
@@ -80,8 +84,8 @@ describe('RelayService', () => {
 		await relayService.removeRelay('https://example.com');
 
 		expect(queueService.deliver).toHaveBeenCalled();
-		expect(queueService.deliver.mock.lastCall![1].type).toBe('Undo');
-		expect(queueService.deliver.mock.lastCall![1].object.type).toBe('Follow');
+		expect(queueService.deliver.mock.lastCall![1]?.type).toBe('Undo');
+		expect(queueService.deliver.mock.lastCall![1]?.object.type).toBe('Follow');
 		expect(queueService.deliver.mock.lastCall![2]).toBe('https://example.com');
 		//expect(queueService.deliver.mock.lastCall![0].username).toBe('relay.actor');
 
diff --git a/packages/backend/test/unit/misc/check-word-mute.ts b/packages/backend/test/unit/misc/check-word-mute.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7ab838bdee78d4cefb81a56336364b25fcf875ec
--- /dev/null
+++ b/packages/backend/test/unit/misc/check-word-mute.ts
@@ -0,0 +1,49 @@
+import { checkWordMute } from '@/misc/check-word-mute.js';
+
+describe(checkWordMute, () => {
+	describe('Slacc boost mode', () => {
+		it('should return false if mutedWords is empty', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [])).toBe(false);
+		});
+		it('should return true if mutedWords is not empty and text contains muted word', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [['foo']])).toBe(true);
+		});
+		it('should return false if mutedWords is not empty and text does not contain muted word', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [['bar']])).toBe(false);
+		});
+		it('should return false when the note is written by me even if mutedWords is not empty and text contains muted word', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo' }, { id: '1' }, [['foo']])).toBe(false);
+		});
+		it('should return true if mutedWords is not empty and text contains muted word in CW', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo', cw: 'bar' }, null, [['bar']])).toBe(true);
+		});
+		it('should return true if mutedWords is not empty and text contains muted word in both CW and text', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo', cw: 'bar' }, null, [['foo'], ['bar']])).toBe(true);
+		});
+		it('should return true if mutedWords is not empty and text does not contain muted word in both CW and text', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo', cw: 'bar' }, null, [['foo'], ['baz']])).toBe(true);
+		});
+	});
+	describe('normal mode', () => {
+		it('should return false if text does not contain muted words', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo' }, null, [['foo', 'bar']])).toBe(false);
+		});
+		it('should return true if text contains muted words', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foobar' }, null, [['foo', 'bar']])).toBe(true);
+		});
+		it('should return false when the note is written by me even if text contains muted words', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo bar' }, { id: '1' }, [['foo', 'bar']])).toBe(false);
+		});
+	});
+	describe('RegExp mode', () => {
+		it('should return false if text does not contain muted words', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo' }, null, ['/bar/'])).toBe(false);
+		});
+		it('should return true if text contains muted words', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foobar' }, null, ['/bar/'])).toBe(true);
+		});
+		it('should return false when the note is written by me even if text contains muted words', async () => {
+			expect(await checkWordMute({ userId: '1', text: 'foo bar' }, { id: '1' }, ['/bar/'])).toBe(false);
+		});
+	});
+});
diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts
index f0827331f7976beb4fcb922a042e0fcd87d654fe..755bec6869f7f5340cb641f11251b8d0a471d873 100644
--- a/packages/frontend/.storybook/changes.ts
+++ b/packages/frontend/.storybook/changes.ts
@@ -38,6 +38,7 @@ fs.readFile(
 					path.resolve(__dirname, '../../..', arg)
 				)
 			)
+			.map((path) => path.replace(/(?:(?<=\.stories)\.(?:impl|meta)|\.msw)(?=\.ts$)/g, ''))
 			.map((path) => (path.startsWith('.') ? path : `./${path}`))
 	);
 	if (
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index dd40bac2ccd27b09ca9aaa02b39975bd01639bb6..7c51d4c00ce432313fd90182624d1426321a46fb 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -118,7 +118,7 @@ function toStories(component: string): string {
 				.replace(/[-.]|^(?=\d)/g, '_')
 				.replace(/(?<=^[^A-Z_]*$)/, '_')}
 		/> as estree.Identifier;
-	const parameters = (
+	const parameters =
 		<object-expression
 			properties={[
 				<property
@@ -137,9 +137,8 @@ function toStories(component: string): string {
 					  ]
 					: []),
 			]}
-		/>
-	) as estree.ObjectExpression;
-	const program = (
+		/> as estree.ObjectExpression;
+	const program =
 		<program
 			body={[
 				<import-declaration
@@ -379,11 +378,11 @@ function toStories(component: string): string {
 					declaration={(<identifier name='meta' />) as estree.Identifier}
 				/> as estree.ExportDefaultDeclaration,
 			]}
-		/>
-	) as estree.Program;
+		/> as estree.Program;
 	return format(
 		'/* eslint-disable @typescript-eslint/explicit-function-return-type */\n' +
 			'/* eslint-disable import/no-default-export */\n' +
+			'/* eslint-disable import/no-duplicates */\n' +
 			generate(program, { generator }) +
 			(hasImplStories ? readFileSync(`${implStories}.ts`, 'utf-8') : ''),
 		{
@@ -397,7 +396,11 @@ function toStories(component: string): string {
 // glob('src/{components,pages,ui,widgets}/**/*.vue')
 Promise.all([
 	glob('src/components/global/*.vue'),
+	glob('src/components/Mk{A,B}*.vue'),
 	glob('src/components/MkGalleryPostPreview.vue'),
+	glob('src/components/MkSignupServerRules.vue'),
+	glob('src/components/MkUserSetupDialog.vue'),
+	glob('src/components/MkUserSetupDialog.*.vue'),
 	glob('src/pages/user/home.vue'),
 ])
 	.then((globs) => globs.flat())
diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts
index 45db48fa1da6ae691a5f72a5a98d4f78cfc0c51e..1d0ce5ab63c914bb5f72baceef418ed6b9b6a78f 100644
--- a/packages/frontend/.storybook/main.ts
+++ b/packages/frontend/.storybook/main.ts
@@ -1,6 +1,6 @@
 import { resolve } from 'node:path';
 import type { StorybookConfig } from '@storybook/vue3-vite';
-import { mergeConfig } from 'vite';
+import { type Plugin, mergeConfig } from 'vite';
 import turbosnap from 'vite-plugin-turbosnap';
 const config = {
 	stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
@@ -22,6 +22,10 @@ const config = {
 		disableTelemetry: true,
 	},
 	async viteFinal(config) {
+		const replacePluginForIsChromatic = config.plugins?.findIndex((plugin) => plugin && (plugin as Partial<Plugin>)?.name === 'replace') ?? -1;
+		if (~replacePluginForIsChromatic) {
+			config.plugins?.splice(replacePluginForIsChromatic, 1);
+		}
 		return mergeConfig(config, {
 			plugins: [
 				turbosnap({
diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts
index 41c3c5c4d926ae7d1aec8ec556257cd5e4cd94f7..4091e39686fbce8282d52f72b0eef7371717f06a 100644
--- a/packages/frontend/.storybook/mocks.ts
+++ b/packages/frontend/.storybook/mocks.ts
@@ -8,6 +8,16 @@ export const onUnhandledRequest = ((req, print) => {
 }) satisfies SharedOptions['onUnhandledRequest'];
 
 export const commonHandlers = [
+	rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => {
+		const { codepoints } = req.params;
+		const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
+		return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+	}),
+	rest.get('/fluent-emojis/:codepoints.png', async (req, res, ctx) => {
+		const { codepoints } = req.params;
+		const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
+		return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+	}),
 	rest.get('/twemoji/:codepoints.svg', async (req, res, ctx) => {
 		const { codepoints } = req.params;
 		const value = await fetch(`https://unpkg.com/@discordapp/twemoji@14.1.2/dist/svg/${codepoints}.svg`).then((response) => response.blob());
diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html
index 64e537b9310b295bfcc95f8a2b06c899af4299ad..ab694f64fb9876c7aeb014b7717bc4ef948fe11c 100644
--- a/packages/frontend/.storybook/preview-head.html
+++ b/packages/frontend/.storybook/preview-head.html
@@ -1,3 +1,5 @@
+<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
+<link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
 <link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.12.0/tabler-icons.min.css">
 <link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
 <style>
diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts
index b2974276ab96ab0e08ae1c49dcd5a11d4035b0d1..e887acaa2e8fab5ecb14dc8648f03223797ffd00 100644
--- a/packages/frontend/.storybook/preview.ts
+++ b/packages/frontend/.storybook/preview.ts
@@ -3,6 +3,7 @@ import { FORCE_REMOUNT } from '@storybook/core-events';
 import { type Preview, setup } from '@storybook/vue3';
 import isChromatic from 'chromatic/isChromatic';
 import { initialize, mswDecorator } from 'msw-storybook-addon';
+import { userDetailed } from './fakes';
 import locale from './locale';
 import { commonHandlers, onUnhandledRequest } from './mocks';
 import themes from './themes';
@@ -10,6 +11,7 @@ import '../src/style.scss';
 
 const appInitialized = Symbol();
 
+let lastStory = null;
 let moduleInitialized = false;
 let unobserve = () => {};
 let misskeyOS = null;
@@ -19,7 +21,7 @@ function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme
 	const theme = themes[document.documentElement.dataset.misskeyTheme];
 	if (theme) {
 		applyTheme(themes[document.documentElement.dataset.misskeyTheme]);
-	} else if (isChromatic()) {
+	} else {
 		applyTheme(themes['l-light']);
 	}
 	const observer = new MutationObserver((entries) => {
@@ -42,10 +44,19 @@ function loadTheme(applyTheme: typeof import('../src/scripts/theme')['applyTheme
 	unobserve = () => observer.disconnect();
 }
 
+function initLocalStorage() {
+	localStorage.clear();
+	localStorage.setItem('account', JSON.stringify({
+		...userDetailed(),
+		policies: {},
+	}));
+	localStorage.setItem('locale', JSON.stringify(locale));
+}
+
 initialize({
 	onUnhandledRequest,
 });
-localStorage.setItem("locale", JSON.stringify(locale));
+initLocalStorage();
 queueMicrotask(() => {
 	Promise.all([
 		import('../src/components'),
@@ -76,6 +87,27 @@ queueMicrotask(() => {
 const preview = {
 	decorators: [
 		(Story, context) => {
+			if (lastStory === context.id) {
+				lastStory = null;
+			} else {
+				lastStory = context.id;
+				const channel = addons.getChannel();
+				const resetIndexedDBPromise = globalThis.indexedDB?.databases
+					? indexedDB.databases().then((r) => {
+							for (var i = 0; i < r.length; i++) {
+								indexedDB.deleteDatabase(r[i].name!);
+							}
+						}).catch(() => {})
+					: Promise.resolve();
+				const resetDefaultStorePromise = import('../src/store').then(({ defaultStore }) => {
+					// @ts-expect-error
+					defaultStore.init();
+				}).catch(() => {});
+				Promise.all([resetIndexedDBPromise, resetDefaultStorePromise]).then(() => {
+					initLocalStorage();
+					channel.emit(FORCE_REMOUNT, { storyId: context.id });
+				});
+			}
 			const story = Story();
 			if (!moduleInitialized) {
 				const channel = addons.getChannel();
diff --git a/packages/frontend/.vscode/storybook.code-snippets b/packages/frontend/.vscode/storybook.code-snippets
new file mode 100644
index 0000000000000000000000000000000000000000..785d0a16081a8805ddb3421e4c3b9bb20dee4a2e
--- /dev/null
+++ b/packages/frontend/.vscode/storybook.code-snippets
@@ -0,0 +1,84 @@
+{
+	"Storybook Story Impl File": {
+		"scope": "typescript",
+		"prefix": "storyimpl",
+		"body": [
+			"/* eslint-disable @typescript-eslint/explicit-function-return-type */",
+			"import { StoryObj } from '@storybook/vue3';",
+			"import $1 from './$1.vue';",
+			"export const Default = {",
+			"\trender(args) {",
+			"\t\treturn {",
+			"\t\t\tcomponents: {",
+			"\t\t\t\t$1,",
+			"\t\t\t},",
+			"\t\t\tsetup() {",
+			"\t\t\t\treturn {",
+			"\t\t\t\t\targs,",
+			"\t\t\t\t};",
+			"\t\t\t},",
+			"\t\t\tcomputed: {",
+			"\t\t\t\tprops() {",
+			"\t\t\t\t\treturn {",
+			"\t\t\t\t\t\t...this.args,",
+			"\t\t\t\t\t};",
+			"\t\t\t\t},",
+			"\t\t\t},",
+			"\t\t\ttemplate: '<$1 v-bind=\"props\" />',",
+			"\t\t};",
+			"\t},",
+			"\targs: {",
+			"\t\t$2",
+			"\t},",
+			"\tparameters: {",
+			"\t\tlayout: 'centered',",
+			"\t},",
+			"} satisfies StoryObj<typeof $1>;",
+			""
+		]
+	},
+	"Storybook Story Impl File (w/ events)": {
+		"scope": "typescript",
+		"prefix": "storyimplevent",
+		"body": [
+			"/* eslint-disable @typescript-eslint/explicit-function-return-type */",
+			"import { action } from '@storybook/addon-actions';",
+			"import { StoryObj } from '@storybook/vue3';",
+			"import $1 from './$1.vue';",
+			"export const Default = {",
+			"\trender(args) {",
+			"\t\treturn {",
+			"\t\t\tcomponents: {",
+			"\t\t\t\t$1,",
+			"\t\t\t},",
+			"\t\t\tsetup() {",
+			"\t\t\t\treturn {",
+			"\t\t\t\t\targs,",
+			"\t\t\t\t};",
+			"\t\t\t},",
+			"\t\t\tcomputed: {",
+			"\t\t\t\tprops() {",
+			"\t\t\t\t\treturn {",
+			"\t\t\t\t\t\t...this.args,",
+			"\t\t\t\t\t};",
+			"\t\t\t\t},",
+			"\t\t\t\tevents() {",
+			"\t\t\t\t\treturn {",
+			"\t\t\t\t\t\t$3",
+			"\t\t\t\t\t};",
+			"\t\t\t\t},",
+			"\t\t\t},",
+			"\t\t\ttemplate: '<$1 v-bind=\"props\" v-on=\"events\" />',",
+			"\t\t};",
+			"\t},",
+			"\targs: {",
+			"\t\t$2",
+			"\t},",
+			"\tparameters: {",
+			"\t\tlayout: 'centered',",
+			"\t},",
+			"} satisfies StoryObj<typeof $1>;",
+			""
+		]
+	}
+}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 79fb626a9a9eaac1c06f0f9d09ac9208a25fc7b7..7646e152f9e6f2163238b2bc1cb91bb820c47c8e 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -15,28 +15,31 @@
 	},
 	"dependencies": {
 		"@discordapp/twemoji": "14.1.2",
-		"@rollup/plugin-alias": "4.0.3",
+		"@rollup/plugin-alias": "5.0.0",
 		"@rollup/plugin-json": "6.0.0",
+		"@rollup/plugin-replace": "^5.0.2",
 		"@rollup/pluginutils": "5.0.2",
-		"@syuilo/aiscript": "0.13.1",
-		"@tabler/icons-webfont": "2.12.0",
-		"@vitejs/plugin-vue": "4.1.0",
+		"@syuilo/aiscript": "0.13.2",
+		"@tabler/icons-webfont": "2.17.0",
+		"@vitejs/plugin-vue": "4.2.1",
+		"@vue-macros/reactivity-transform": "^0.3.5",
 		"@vue/compiler-sfc": "3.2.47",
 		"autosize": "5.0.2",
 		"blurhash": "2.0.5",
 		"broadcast-channel": "4.20.2",
 		"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
 		"canvas-confetti": "1.6.0",
-		"chart.js": "4.2.1",
+		"chart.js": "4.3.0",
 		"chartjs-adapter-date-fns": "3.0.0",
 		"chartjs-chart-matrix": "2.0.1",
 		"chartjs-plugin-gradient": "0.6.1",
 		"chartjs-plugin-zoom": "2.0.1",
+		"chromatic": "6.17.3",
 		"compare-versions": "5.0.1",
 		"cropperjs": "2.0.0-beta.2",
-		"date-fns": "2.29.3",
+		"date-fns": "2.30.0",
 		"escape-regexp": "0.0.1",
-		"eventemitter3": "5.0.0",
+		"eventemitter3": "5.0.1",
 		"gsap": "3.11.5",
 		"idb-keyval": "6.2.0",
 		"insert-text-at-cursor": "0.3.0",
@@ -50,10 +53,10 @@
 		"punycode": "2.3.0",
 		"querystring": "0.2.1",
 		"rndstr": "1.0.0",
-		"rollup": "3.20.2",
+		"rollup": "3.21.3",
 		"s-age": "1.1.2",
 		"sanitize-html": "2.10.0",
-		"sass": "1.60.0",
+		"sass": "1.62.1",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
 		"syuilo-password-strength": "0.0.1",
@@ -61,45 +64,46 @@
 		"three": "0.151.3",
 		"throttle-debounce": "5.0.0",
 		"tinycolor2": "1.6.0",
-		"tsc-alias": "1.8.5",
+		"tsc-alias": "1.8.6",
 		"tsconfig-paths": "4.2.0",
 		"twemoji-parser": "14.0.0",
-		"typescript": "5.0.3",
+		"typescript": "5.0.4",
 		"uuid": "9.0.0",
 		"vanilla-tilt": "1.8.0",
-		"vite": "4.2.1",
+		"vite": "4.3.4",
 		"vue": "3.2.47",
 		"vue-plyr": "7.0.0",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
-		"@storybook/addon-essentials": "7.0.2",
-		"@storybook/addon-interactions": "7.0.2",
-		"@storybook/addon-links": "7.0.2",
-		"@storybook/addon-storysource": "7.0.2",
-		"@storybook/addons": "7.0.2",
-		"@storybook/blocks": "7.0.2",
-		"@storybook/core-events": "7.0.2",
+		"@storybook/addon-actions": "7.0.7",
+		"@storybook/addon-essentials": "7.0.7",
+		"@storybook/addon-interactions": "7.0.7",
+		"@storybook/addon-links": "7.0.7",
+		"@storybook/addon-storysource": "7.0.7",
+		"@storybook/addons": "7.0.7",
+		"@storybook/blocks": "7.0.7",
+		"@storybook/core-events": "7.0.7",
 		"@storybook/jest": "0.1.0",
-		"@storybook/manager-api": "7.0.2",
-		"@storybook/preview-api": "7.0.2",
-		"@storybook/react": "7.0.2",
-		"@storybook/react-vite": "7.0.2",
-		"@storybook/testing-library": "0.0.14-next.1",
-		"@storybook/theming": "7.0.2",
-		"@storybook/types": "7.0.2",
-		"@storybook/vue3": "7.0.2",
-		"@storybook/vue3-vite": "7.0.2",
+		"@storybook/manager-api": "7.0.7",
+		"@storybook/preview-api": "7.0.7",
+		"@storybook/react": "7.0.7",
+		"@storybook/react-vite": "7.0.7",
+		"@storybook/testing-library": "0.1.0",
+		"@storybook/theming": "7.0.7",
+		"@storybook/types": "7.0.7",
+		"@storybook/vue3": "7.0.7",
+		"@storybook/vue3-vite": "7.0.7",
 		"@testing-library/jest-dom": "5.16.5",
 		"@testing-library/vue": "7.0.0",
 		"@types/escape-regexp": "0.0.1",
-		"@types/estree": "1.0.0",
+		"@types/estree": "1.0.1",
 		"@types/gulp": "4.0.10",
 		"@types/gulp-rename": "2.0.1",
 		"@types/matter-js": "0.18.2",
-		"@types/micromatch": "3.1.1",
-		"@types/node": "18.15.11",
+		"@types/micromatch": "4.0.2",
+		"@types/node": "18.16.3",
 		"@types/punycode": "2.1.0",
 		"@types/sanitize-html": "2.9.0",
 		"@types/seedrandom": "3.0.5",
@@ -109,34 +113,33 @@
 		"@types/uuid": "9.0.1",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.4",
-		"@typescript-eslint/eslint-plugin": "5.57.1",
-		"@typescript-eslint/parser": "5.57.1",
-		"@vitest/coverage-c8": "^0.29.8",
+		"@typescript-eslint/eslint-plugin": "5.59.2",
+		"@typescript-eslint/parser": "5.59.2",
+		"@vitest/coverage-c8": "0.30.1",
 		"@vue/runtime-core": "3.2.47",
 		"astring": "1.8.4",
 		"chokidar-cli": "3.0.0",
-		"chromatic": "6.17.3",
 		"cross-env": "7.0.3",
-		"cypress": "12.9.0",
-		"eslint": "8.37.0",
+		"cypress": "12.11.0",
+		"eslint": "8.39.0",
 		"eslint-plugin-import": "2.27.5",
-		"eslint-plugin-vue": "9.10.0",
+		"eslint-plugin-vue": "9.11.0",
 		"fast-glob": "3.2.12",
-		"happy-dom": "8.9.0",
+		"happy-dom": "9.10.2",
 		"micromatch": "3.1.10",
 		"msw": "1.2.1",
 		"msw-storybook-addon": "1.8.0",
-		"prettier": "2.8.7",
+		"prettier": "2.8.8",
 		"react": "18.2.0",
 		"react-dom": "18.2.0",
 		"start-server-and-test": "2.0.0",
-		"storybook": "7.0.2",
+		"storybook": "7.0.7",
 		"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
 		"summaly": "github:misskey-dev/summaly",
-		"vite-plugin-turbosnap": "^1.0.1",
-		"vitest": "0.29.8",
+		"vite-plugin-turbosnap": "1.0.2",
+		"vitest": "0.30.1",
 		"vitest-fetch-mock": "0.2.2",
 		"vue-eslint-parser": "9.1.1",
-		"vue-tsc": "1.2.0"
+		"vue-tsc": "1.6.3"
 	}
 }
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7d27adeb0448a1efe7c8a4cd4274a6f9306d259e
--- /dev/null
+++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
@@ -0,0 +1,49 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { abuseUserReport } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAbuseReport from './MkAbuseReport.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkAbuseReport,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						resolved: action('resolved'),
+					};
+				},
+			},
+			template: '<MkAbuseReport v-bind="props" v-on="events" />',
+		};
+	},
+	args: {
+		report: abuseUserReport(),
+	},
+	parameters: {
+		layout: 'fullscreen',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => {
+					action('POST /api/admin/resolve-abuse-user-report')(await req.json());
+					return res(ctx.json({}));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkAbuseReport>;
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d0877ffd3be6b265a617c8d4a4ea8b6c439c19b0
--- /dev/null
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -0,0 +1,49 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkAbuseReportWindow,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						'closed': action('closed'),
+					};
+				},
+			},
+			template: '<MkAbuseReportWindow v-bind="props" v-on="events" />',
+		};
+	},
+	args: {
+		user: userDetailed(),
+	},
+	parameters: {
+		layout: 'centered',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users/report-abuse', async (req, res, ctx) => {
+					action('POST /api/users/report-abuse')(await req.json());
+					return res(ctx.json({}));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkAbuseReportWindow>;
diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bed9d94311595acb25e4d1e0ae321147569d2faf
--- /dev/null
+++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
@@ -0,0 +1,33 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { userDetailed } from '../../.storybook/fakes';
+import MkAccountMoved from './MkAccountMoved.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkAccountMoved,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkAccountMoved v-bind="props" />',
+		};
+	},
+	args: {
+		username: userDetailed().username,
+		host: userDetailed().host,
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkAccountMoved>;
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index fd472de6c1df50b71004ad36833e3241f23d224f..b02bfdc2b82daa3e4a8973cfaeeab3c43d7b5f80 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -1,8 +1,8 @@
 <template>
-<div :class="$style.root">
+<div v-if="user" :class="$style.root">
 	<i class="ti ti-plane-departure" style="margin-right: 8px;"></i>
 	{{ i18n.ts.accountMoved }}
-	<MkMention :class="$style.link" :username="acct" :host="host ?? localHost"/>
+	<MkMention :class="$style.link" :username="user.username" :host="user.host ?? localHost"/>
 </div>
 </template>
 
@@ -10,11 +10,17 @@
 import MkMention from './MkMention.vue';
 import { i18n } from '@/i18n';
 import { host as localHost } from '@/config';
+import { ref } from 'vue';
+import { UserLite } from 'misskey-js/built/entities';
+import { api } from '@/os';
 
-defineProps<{
-	acct: string;
-	host: string;
+const user = ref<UserLite>();
+
+const props = defineProps<{
+	movedTo: string; // user id
 }>();
+
+api('users/show', { userId: props.movedTo }).then(u => user.value = u);
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..477152a47b672a358f398b2b4a136cd07456e574
--- /dev/null
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -0,0 +1,56 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAchievements from './MkAchievements.vue';
+import { ACHIEVEMENT_TYPES } from '@/scripts/achievements';
+export const Empty = {
+	render(args) {
+		return {
+			components: {
+				MkAchievements,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkAchievements v-bind="props" />',
+		};
+	},
+	args: {
+		user: userDetailed(),
+	},
+	parameters: {
+		layout: 'fullscreen',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users/achievements', (req, res, ctx) => {
+					return res(ctx.json([]));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkAchievements>;
+export const All = {
+	...Empty,
+	parameters: {
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users/achievements', (req, res, ctx) => {
+					return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkAchievements>;
diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
index 05190aa268911c8a8cb959b127e32e7423d7e383..e7fbb472846e28a1bb79355cf56f34b673b51010 100644
--- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
@@ -1,6 +1,7 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
 import MkAnalogClock from './MkAnalogClock.vue';
+import isChromatic from 'chromatic';
 export const Default = {
 	render(args) {
 		return {
@@ -22,6 +23,14 @@ export const Default = {
 			template: '<MkAnalogClock v-bind="props" />',
 		};
 	},
+	args: {
+		now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined,
+	},
+	decorators: [
+		() => ({
+			template: '<div style="container-type:inline-size;height:100%"><div style="height:100cqmin;margin:auto;width:100cqmin"><story/></div></div>',
+		}),
+	],
 	parameters: {
 		layout: 'fullscreen',
 	},
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index 12182026166afa8e558a187b227cfdeb1d180683..f12020f81047e0a391ec342fd10310e01e2e44ff 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -99,6 +99,7 @@ const props = withDefaults(defineProps<{
 	graduations?: 'none' | 'dots' | 'numbers';
 	fadeGraduations?: boolean;
 	sAnimation?: 'none' | 'elastic' | 'easeOut';
+	now?: () => Date;
 }>(), {
 	numbers: false,
 	thickness: 0.1,
@@ -107,6 +108,7 @@ const props = withDefaults(defineProps<{
 	graduations: 'dots',
 	fadeGraduations: true,
 	sAnimation: 'elastic',
+	now: () => new Date(),
 });
 
 const graduationsMajor = computed(() => {
@@ -145,11 +147,17 @@ let disableSAnimate = $ref(false);
 let sOneRound = false;
 
 function tick() {
-	const now = new Date();
-	now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
+	const now = props.now();
+	now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset);
+	const previousS = s;
+	const previousM = m;
+	const previousH = h;
 	s = now.getSeconds();
 	m = now.getMinutes();
 	h = now.getHours();
+	if (previousS === s && previousM === m && previousH === h) {
+		return;
+	}
 	hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
 	mAngle = Math.PI * (m + s / 60) / 30;
 	if (sOneRound) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b67c0e679d5858d873e75c2c39a98520922cf102
--- /dev/null
+++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts
@@ -0,0 +1,2 @@
+import MkAsUi from './MkAsUi.vue';
+void MkAsUi;
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..075904d6a356037c8d93b358f797f3e0775ba354
--- /dev/null
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -0,0 +1,176 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
+import { expect } from '@storybook/jest';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAutocomplete from './MkAutocomplete.vue';
+import MkInput from './MkInput.vue';
+import { tick } from '@/scripts/test-utils';
+const common = {
+	render(args) {
+		return {
+			components: {
+				MkAutocomplete,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+				events() {
+					return {
+						open: action('open'),
+						closed: action('closed'),
+					};
+				},
+			},
+			template: '<MkAutocomplete v-bind="props" v-on="events" :textarea="textarea" />',
+		};
+	},
+	args: {
+		close: action('close'),
+		x: 0,
+		y: 0,
+	},
+	decorators: [
+		(_, context) => ({
+			components: {
+				MkInput,
+			},
+			data() {
+				return {
+					q: context.args.q,
+					textarea: null,
+				};
+			},
+			methods: {
+				inputMounted() {
+					this.textarea = this.$refs.input.$refs.inputEl;
+				},
+			},
+			template: '<MkInput v-model="q" ref="input" @vue:mounted="inputMounted"/><story v-if="textarea" :q="q" :textarea="textarea"/>',
+		}),
+	],
+	parameters: {
+		controls: {
+			exclude: ['textarea'],
+		},
+		layout: 'centered',
+		chromatic: {
+			// FIXME: flaky
+			disableSnapshot: true,
+		},
+	},
+} satisfies StoryObj<typeof MkAutocomplete>;
+export const User = {
+	...common,
+	args: {
+		...common.args,
+		type: 'user',
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		const input = canvas.getByRole('combobox');
+		await waitFor(() => userEvent.hover(input));
+		await waitFor(() => userEvent.click(input));
+		await waitFor(() => userEvent.type(input, 'm'));
+		await waitFor(async () => {
+			await userEvent.type(input, ' ', { delay: 256 });
+			await tick();
+			return await expect(canvas.getByRole('list')).toBeInTheDocument();
+		}, { timeout: 16384 });
+	},
+	parameters: {
+		...common.parameters,
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => {
+					return res(ctx.json([
+						userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
+						userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
+					]));
+				}),
+			],
+		},
+	},
+};
+export const Hashtag = {
+	...common,
+	args: {
+		...common.args,
+		type: 'hashtag',
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		const input = canvas.getByRole('combobox');
+		await waitFor(() => userEvent.hover(input));
+		await waitFor(() => userEvent.click(input));
+		await waitFor(() => userEvent.type(input, '気象'));
+		await waitFor(async () => {
+			await userEvent.type(input, ' ', { delay: 256 });
+			await tick();
+			return await expect(canvas.getByRole('list')).toBeInTheDocument();
+		}, { interval: 256, timeout: 16384 });
+	},
+	parameters: {
+		...common.parameters,
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/hashtags/search', (req, res, ctx) => {
+					return res(ctx.json([
+						'気象警報注意報',
+						'気象警報',
+						'気象情報',
+					]));
+				}),
+			],
+		},
+	},
+};
+export const Emoji = {
+	...common,
+	args: {
+		...common.args,
+		type: 'emoji',
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		const input = canvas.getByRole('combobox');
+		await waitFor(() => userEvent.hover(input));
+		await waitFor(() => userEvent.click(input));
+		await waitFor(() => userEvent.type(input, 'smile'));
+		await waitFor(async () => {
+			await userEvent.type(input, ' ', { delay: 256 });
+			await tick();
+			return await expect(canvas.getByRole('list')).toBeInTheDocument();
+		}, { interval: 256, timeout: 16384 });
+	},
+} satisfies StoryObj<typeof MkAutocomplete>;
+export const MfmTag = {
+	...common,
+	args: {
+		...common.args,
+		type: 'mfmTag',
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		const input = canvas.getByRole('combobox');
+		await waitFor(() => userEvent.hover(input));
+		await waitFor(() => userEvent.click(input));
+		await waitFor(async () => {
+			await tick();
+			return await expect(canvas.getByRole('list')).toBeInTheDocument();
+		}, { interval: 256, timeout: 16384 });
+	},
+} satisfies StoryObj<typeof MkAutocomplete>;
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..14052c7343b490c9dc2ba20b1725b1b8f423c2f6
--- /dev/null
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -0,0 +1,46 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { userDetailed } from '../../.storybook/fakes';
+import { commonHandlers } from '../../.storybook/mocks';
+import MkAvatars from './MkAvatars.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkAvatars,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkAvatars v-bind="props" />',
+		};
+	},
+	args: {
+		userIds: ['17', '20', '18'],
+	},
+	parameters: {
+		layout: 'centered',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users/show', (req, res, ctx) => {
+					return res(ctx.json([
+						userDetailed('17'),
+						userDetailed('20'),
+						userDetailed('18'),
+					]));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkAvatars>;
diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts
index e1c1c54d10f07233b3bf5a2fc724d7a77296ef5d..982a8b3be126d06aef3f11a6055d02ab7076c52f 100644
--- a/packages/frontend/src/components/MkButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkButton.stories.impl.ts
@@ -1,6 +1,6 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 /* eslint-disable import/no-default-export */
-/* eslint-disable import/no-duplicates */
+import { action } from '@storybook/addon-actions';
 import { StoryObj } from '@storybook/vue3';
 import MkButton from './MkButton.vue';
 export const Default = {
@@ -20,11 +20,60 @@ export const Default = {
 						...this.args,
 					};
 				},
+				events() {
+					return {
+						click: action('click'),
+					};
+				},
 			},
-			template: '<MkButton v-bind="props">Text</MkButton>',
+			template: '<MkButton v-bind="props" v-on="events">Text</MkButton>',
 		};
 	},
+	args: {
+	},
 	parameters: {
 		layout: 'centered',
 	},
 } satisfies StoryObj<typeof MkButton>;
+export const Primary = {
+	...Default,
+	args: {
+		...Default.args,
+		primary: true,
+	},
+} satisfies StoryObj<typeof MkButton>;
+export const Gradate = {
+	...Default,
+	args: {
+		...Default.args,
+		gradate: true,
+	},
+} satisfies StoryObj<typeof MkButton>;
+export const Rounded = {
+	...Default,
+	args: {
+		...Default.args,
+		rounded: true,
+	},
+} satisfies StoryObj<typeof MkButton>;
+export const Danger = {
+	...Default,
+	args: {
+		...Default.args,
+		danger: true,
+	},
+} satisfies StoryObj<typeof MkButton>;
+export const Small = {
+	...Default,
+	args: {
+		...Default.args,
+		small: true,
+	},
+} satisfies StoryObj<typeof MkButton>;
+export const Large = {
+	...Default,
+	args: {
+		...Default.args,
+		large: true,
+	},
+} satisfies StoryObj<typeof MkButton>;
diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue
new file mode 100644
index 0000000000000000000000000000000000000000..2471aa958dc6157524b93a8dd4ddc6b8beabd2d1
--- /dev/null
+++ b/packages/frontend/src/components/MkColorInput.vue
@@ -0,0 +1,110 @@
+<template>
+<div>
+	<div :class="$style.label"><slot name="label"></slot></div>
+	<div :class="[$style.input, { disabled }]">
+		<input
+			ref="inputEl"
+			v-model="v"
+			v-adaptive-border
+			:class="$style.inputCore"
+			type="color"
+			:disabled="disabled"
+			:required="required"
+			:readonly="readonly"
+			@input="onInput"
+		>
+	</div>
+	<div :class="$style.caption"><slot name="caption"></slot></div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
+import { i18n } from '@/i18n';
+
+const props = defineProps<{
+	modelValue: string | null;
+	required?: boolean;
+	readonly?: boolean;
+	disabled?: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'update:modelValue', value: string): void;
+}>();
+
+const { modelValue } = toRefs(props);
+const v = ref(modelValue.value);
+const inputEl = shallowRef<HTMLElement>();
+
+const onInput = (ev: KeyboardEvent) => {
+	emit('update:modelValue', v.value);
+};
+</script>
+
+<style lang="scss" module>
+.label {
+	font-size: 0.85em;
+	padding: 0 0 8px 0;
+	user-select: none;
+
+	&:empty {
+		display: none;
+	}
+}
+
+.caption {
+	font-size: 0.85em;
+	padding: 8px 0 0 0;
+	color: var(--fgTransparentWeak);
+
+	&:empty {
+		display: none;
+	}
+}
+
+.input {
+	position: relative;
+
+	&.focused {
+		> .inputCore {
+			border-color: var(--accent) !important;
+			//box-shadow: 0 0 0 4px var(--focus);
+		}
+	}
+
+	&.disabled {
+		opacity: 0.7;
+
+		&,
+		> .inputCore {
+			cursor: not-allowed !important;
+		}
+	}
+}
+
+.inputCore {
+	appearance: none;
+	-webkit-appearance: none;
+	display: block;
+	height: 42px;
+	width: 100%;
+	margin: 0;
+	padding: 0 12px;
+	font: inherit;
+	font-weight: normal;
+	font-size: 1em;
+	color: var(--fg);
+	background: var(--panel);
+	border: solid 1px var(--panel);
+	border-radius: 6px;
+	outline: none;
+	box-shadow: none;
+	box-sizing: border-box;
+	transition: border-color 0.1s ease-out;
+
+	&:hover {
+		border-color: var(--inputBorderHover) !important;
+	}
+}
+</style>
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index a6372b7b6fef64d179d22703cede02596a50d1e9..d03331a6ebc14c9875dab31920b3ea431152436b 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -1,6 +1,6 @@
 <template>
-<div class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]">
-	<header v-if="showHeader" ref="header" :class="$style.header">
+<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]">
+	<header v-if="showHeader" ref="headerEl" :class="$style.header">
 		<div :class="$style.title">
 			<span :class="$style.titleIcon"><slot name="icon"></slot></span>
 			<slot name="header"></slot>
@@ -23,7 +23,7 @@
 		@leave="leave"
 		@after-leave="afterLeave"
 	>
-		<div v-show="showBody" ref="content" :class="[$style.content, { [$style.omitted]: omitted }]">
+		<div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]">
 			<slot></slot>
 			<button v-if="omitted" :class="$style.fade" class="_button" @click="() => { ignoreOmit = true; omitted = false; }">
 				<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
@@ -33,109 +33,80 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, ref, shallowRef, watch } from 'vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	props: {
-		showHeader: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		thin: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		naked: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		foldable: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		expanded: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		scrollable: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		maxHeight: {
-			type: Number,
-			required: false,
-			default: null,
-		},
-	},
-	data() {
-		return {
-			showBody: this.expanded,
-			omitted: null,
-			ignoreOmit: false,
-			defaultStore,
-			i18n,
-		};
-	},
-	mounted() {
-		this.$watch('showBody', showBody => {
-			const headerHeight = this.showHeader ? this.$refs.header.offsetHeight : 0;
-			this.$el.style.minHeight = `${headerHeight}px`;
-			if (showBody) {
-				this.$el.style.flexBasis = 'auto';
-			} else {
-				this.$el.style.flexBasis = `${headerHeight}px`;
-			}
-		}, {
-			immediate: true,
-		});
+const props = withDefaults(defineProps<{
+	showHeader?: boolean;
+	thin?: boolean;
+	naked?: boolean;
+	foldable?: boolean;
+	scrollable?: boolean;
+	expanded?: boolean;
+	maxHeight?: number | null;
+}>(), {
+	expanded: true,
+	showHeader: true,
+	maxHeight: null,
+});
 
-		this.$el.style.setProperty('--maxHeight', this.maxHeight + 'px');
+const rootEl = shallowRef<HTMLElement>();
+const contentEl = shallowRef<HTMLElement>();
+const headerEl = shallowRef<HTMLElement>();
+const showBody = ref(props.expanded);
+const ignoreOmit = ref(false);
+const omitted = ref(false);
+
+function enter(el) {
+	const elementHeight = el.getBoundingClientRect().height;
+	el.style.height = 0;
+	el.offsetHeight; // reflow
+	el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px';
+}
 
-		const calcOmit = () => {
-			if (this.omitted || this.ignoreOmit || this.maxHeight == null) return;
-			const height = this.$refs.content.offsetHeight;
-			this.omitted = height > this.maxHeight;
-		};
+function afterEnter(el) {
+	el.style.height = null;
+}
 
-		calcOmit();
-		new ResizeObserver((entries, observer) => {
-			calcOmit();
-		}).observe(this.$refs.content);
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			if (!this.foldable) return;
-			this.showBody = show;
-		},
+function leave(el) {
+	const elementHeight = el.getBoundingClientRect().height;
+	el.style.height = elementHeight + 'px';
+	el.offsetHeight; // reflow
+	el.style.height = 0;
+}
 
-		enter(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = 0;
-			el.offsetHeight; // reflow
-			el.style.height = elementHeight + 'px';
-		},
-		afterEnter(el) {
-			el.style.height = null;
-		},
-		leave(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = elementHeight + 'px';
-			el.offsetHeight; // reflow
-			el.style.height = 0;
-		},
-		afterLeave(el) {
-			el.style.height = null;
-		},
-	},
+function afterLeave(el) {
+	el.style.height = null;
+}
+
+const calcOmit = () => {
+	if (omitted.value || ignoreOmit.value || props.maxHeight == null) return;
+	const height = contentEl.value.offsetHeight;
+	omitted.value = height > props.maxHeight;
+};
+
+onMounted(() => {
+	watch(showBody, v => {
+		const headerHeight = props.showHeader ? headerEl.value.offsetHeight : 0;
+		rootEl.value.style.minHeight = `${headerHeight}px`;
+		if (v) {
+			rootEl.value.style.flexBasis = 'auto';
+		} else {
+			rootEl.value.style.flexBasis = `${headerHeight}px`;
+		}
+	}, {
+		immediate: true,
+	});
+
+	rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px');
+
+	calcOmit();
+
+	new ResizeObserver((entries, observer) => {
+		calcOmit();
+	}).observe(contentEl.value);
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 93c1f89199b749c672ae5bb35dba6a02fe2593b4..9f5404ce15587921f34aed1c5ddaf1440b73941d 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -9,7 +9,7 @@
 			<i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i>
 			<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i>
 			<i v-else-if="type === 'info'" :class="$style.iconInner" class="ti ti-info-circle"></i>
-			<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-question-circle"></i>
+			<i v-else-if="type === 'question'" :class="$style.iconInner" class="ti ti-help-circle"></i>
 			<MkLoading v-else-if="type === 'waiting'" :class="$style.iconInner" :em="true"/>
 		</div>
 		<header v-if="title" :class="$style.title"><Mfm :text="title"/></header>
@@ -32,8 +32,8 @@
 			</template>
 		</MkSelect>
 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
-			<MkButton v-if="showOkButton" inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
-			<MkButton v-if="showCancelButton || input || select" inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
+			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabled" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
+			<MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton>
 		</div>
 		<div v-if="actions" :class="$style.buttons">
 			<MkButton v-for="action in actions" :key="action.text" inline rounded :primary="action.primary" :danger="action.danger" @click="() => { action.callback(); modal?.close(); }">{{ action.text }}</MkButton>
@@ -183,7 +183,7 @@ onBeforeUnmount(() => {
 	box-sizing: border-box;
 	text-align: center;
 	background: var(--panel);
-	border-radius: var(--radius);
+	border-radius: 16px;
 }
 
 .icon {
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 58cc0de5c8cd8109ee834791e1df2fabc771ff32..10eee6aab106c5bd03d2ed26d8774041a0f83832 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -1,8 +1,8 @@
 <template>
-<div ref="rootEl" :class="$style.root">
+<div ref="rootEl" :class="$style.root" role="group" :aria-expanded="opened">
 	<MkStickyContainer>
 		<template #header>
-			<div :class="[$style.header, { [$style.opened]: opened }]" class="_button" @click="toggle">
+			<div :class="[$style.header, { [$style.opened]: opened }]" class="_button" role="button" data-cy-folder-header @click="toggle">
 				<div :class="$style.headerIcon"><slot name="icon"></slot></div>
 				<div :class="$style.headerText">
 					<div :class="$style.headerTextMain">
@@ -20,7 +20,7 @@
 			</div>
 		</template>
 
-		<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }">
+		<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }" :aria-hidden="!opened">
 			<Transition
 				:enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
 				:leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
@@ -65,7 +65,7 @@ const getBgColor = (el: HTMLElement) => {
 	}
 };
 
-let rootEl = $ref<HTMLElement>();
+let rootEl = $shallowRef<HTMLElement>();
 let bgSame = $ref(false);
 let opened = $ref(props.defaultOpen);
 let openedAtLeastOnce = $ref(props.defaultOpen);
@@ -196,7 +196,7 @@ onMounted(() => {
 
 .headerRight {
 	margin-left: auto;
-	opacity: 0.7;
+	color: var(--fgTransparentWeak);
 	white-space: nowrap;
 }
 
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index de8db54bfad047b445b318c10d151a004842681f..beee21c647c3f83f55450b6bc02e9a3d2b7ed9e1 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -178,7 +178,7 @@ onBeforeUnmount(() => {
 	}
 
 	&.active {
-		color: #fff;
+		color: var(--fgOnAccent);
 		background: var(--accent);
 
 		&:hover {
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
index e46a70819252df5d760c915c51f3ff17254ef643..57b3e755132f9274d8fbc4cf1a4ee1fdfa6c04e1 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
@@ -28,9 +28,11 @@ export const Default = {
 	async play({ canvasElement }) {
 		const canvas = within(canvasElement);
 		const links = canvas.getAllByRole('link');
-		await expect(links).toHaveLength(2);
-		await expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`);
-		await expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`);
+		expect(links).toHaveLength(2);
+		expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`);
+		expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`);
+		const images = canvas.getAllByRole<HTMLImageElement>('img');
+		await waitFor(() => expect(Promise.all(images.map((image) => image.decode()))).resolves.toBeDefined());
 	},
 	args: {
 		post: galleryPost(),
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue
index 944f5ad97bc98ce6226edd66c53ad3149144be99..4f8f7b945ac65ecd6e1fd64b04cc00dc9f0c324c 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.vue
+++ b/packages/frontend/src/components/MkGalleryPostPreview.vue
@@ -1,9 +1,21 @@
 <template>
 <MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover">
 	<div class="thumbnail">
-		<ImgWithBlurhash class="img" :hash="post.files[0].blurhash"/>
 		<Transition>
-			<ImgWithBlurhash v-if="show" class="img layered" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/>
+			<ImgWithBlurhash
+				class="img layered"
+				:transition="safe ? null : {
+					enterActiveClass: $style.transition_toggle_enterActive,
+					leaveActiveClass: $style.transition_toggle_leaveActive,
+					enterFromClass: $style.transition_toggle_enterFrom,
+					leaveToClass: $style.transition_toggle_leaveTo,
+					enterToClass: $style.transition_toggle_enterTo,
+					leaveFromClass: $style.transition_toggle_leaveFrom,
+				}"
+				:src="post.files[0].thumbnailUrl"
+				:hash="post.files[0].blurhash"
+				:force-blurhash="!show"
+			/>
 		</Transition>
 	</div>
 	<article>
@@ -28,7 +40,8 @@ const props = defineProps<{
 }>();
 
 const hover = ref(false);
-const show = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive || hover.value);
+const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive);
+const show = computed(() => safe.value || hover.value);
 
 function enterHover(): void {
 	hover.value = true;
@@ -39,6 +52,27 @@ function leaveHover(): void {
 }
 </script>
 
+<style lang="scss" module>
+.transition_toggle_enterActive,
+.transition_toggle_leaveActive {
+	transition: opacity 0.5s;
+	position: absolute;
+	top: 0;
+	left: 0;
+}
+
+.transition_toggle_enterFrom,
+.transition_toggle_leaveTo {
+	opacity: 0;
+}
+
+.transition_toggle_enterTo,
+.transition_toggle_leaveFrom {
+	transition: none;
+	opacity: 1;
+}
+</style>
+
 <style lang="scss" scoped>
 .ttasepnz {
 	display: block;
@@ -66,7 +100,7 @@ function leaveHover(): void {
 		width: 100%;
 		height: 100%;
 		position: absolute;
-		transition: all 0.5s ease;
+		transition: transform 0.5s ease;
 
 		> .img {
 			width: 100%;
@@ -76,16 +110,6 @@ function leaveHover(): void {
 			&.layered {
 				position: absolute;
 				top: 0;
-
-				&.v-enter-active,
-				&.v-leave-active {
-					transition: opacity 0.5s ease;
-				}
-
-				&.v-enter-from,
-				&.v-leave-to {
-					opacity: 0;
-				}
 			}
 		}
 	}
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 944c76d7dc8bae860cc3c3aeaefc668834ec18b0..6406a350601716d30d156bbb9a8fdbea0e457f8b 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -1,44 +1,90 @@
 <template>
-<div :class="[$style.root, { [$style.cover]: cover }]" :title="title">
-	<canvas v-if="!loaded" ref="canvas" :class="$style.canvas" :width="size" :height="size" :title="title"/>
-	<img v-if="src" :class="$style.img" :src="src" :title="title" :alt="alt" @load="onLoad"/>
+<div :class="[$style.root, { [$style.cover]: cover }]" :title="title ?? ''">
+	<img v-if="!loaded && src && !forceBlurhash" :class="$style.loader" :src="src" @load="onLoad"/>
+	<Transition
+		mode="in-out"
+		:enter-active-class="defaultStore.state.animation && (props.transition?.enterActiveClass ?? $style['transition_toggle_enterActive']) || undefined"
+		:leave-active-class="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style['transition_toggle_leaveActive']) || undefined"
+		:enter-from-class="defaultStore.state.animation && props.transition?.enterFromClass || undefined"
+		:leave-to-class="defaultStore.state.animation && props.transition?.leaveToClass || undefined"
+		:enter-to-class="defaultStore.state.animation && (props.transition?.enterToClass ?? $style['transition_toggle_enterTo']) || undefined"
+		:leave-from-class="defaultStore.state.animation && (props.transition?.leaveFromClass ?? $style['transition_toggle_leaveFrom']) || undefined"
+	>
+		<canvas v-if="!loaded || forceBlurhash" ref="canvas" :class="$style.canvas" :width="width" :height="height" :title="title ?? undefined"/>
+		<img v-else :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined"/>
+	</Transition>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, useCssModule, watch } from 'vue';
 import { decode } from 'blurhash';
+import { defaultStore } from '@/store';
+
+const $style = useCssModule();
 
 const props = withDefaults(defineProps<{
+	transition?: {
+		enterActiveClass?: string;
+		leaveActiveClass?: string;
+		enterFromClass?: string;
+		leaveToClass?: string;
+		enterToClass?: string;
+		leaveFromClass?: string;
+	} | null;
 	src?: string | null;
 	hash?: string;
-	alt?: string;
+	alt?: string | null;
 	title?: string | null;
-	size?: number;
+	height?: number;
+	width?: number;
 	cover?: boolean;
+	forceBlurhash?: boolean;
 }>(), {
+	transition: null,
 	src: null,
 	alt: '',
 	title: null,
-	size: 64,
+	height: 64,
+	width: 64,
 	cover: true,
+	forceBlurhash: false,
 });
 
-const canvas = $shallowRef<HTMLCanvasElement>();
+const canvas = shallowRef<HTMLCanvasElement>();
 let loaded = $ref(false);
+let width = $ref(props.width);
+let height = $ref(props.height);
+
+function onLoad() {
+	loaded = true;
+}
+
+watch([() => props.width, () => props.height], () => {
+	const ratio = props.width / props.height;
+	if (ratio > 1) {
+		width = Math.round(64 * ratio);
+		height = 64;
+	} else {
+		width = 64;
+		height = Math.round(64 / ratio);
+	}
+}, {
+	immediate: true,
+});
 
 function draw() {
-	if (props.hash == null) return;
-	const pixels = decode(props.hash, props.size, props.size);
-	const ctx = canvas.getContext('2d');
-	const imageData = ctx!.createImageData(props.size, props.size);
+	if (props.hash == null || !canvas.value) return;
+	const pixels = decode(props.hash, width, height);
+	const ctx = canvas.value.getContext('2d');
+	const imageData = ctx!.createImageData(width, height);
 	imageData.data.set(pixels);
 	ctx!.putImageData(imageData, 0, 0);
 }
 
-function onLoad() {
-	loaded = true;
-}
+watch([() => props.hash, canvas], () => {
+	draw();
+});
 
 onMounted(() => {
 	draw();
@@ -46,12 +92,33 @@ onMounted(() => {
 </script>
 
 <style lang="scss" module>
+.transition_toggle_enterActive,
+.transition_toggle_leaveActive {
+	position: absolute;
+	top: 0;
+	left: 0;
+}
+
+.transition_toggle_enterTo,
+.transition_toggle_leaveFrom {
+	opacity: 0;
+}
+
+.loader {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 0;
+	height: 0;
+}
+
 .root {
 	position: relative;
 	width: 100%;
 	height: 100%;
 
 	&.cover {
+		> .canvas,
 		> .img {
 			object-fit: cover;
 		}
@@ -66,8 +133,7 @@ onMounted(() => {
 }
 
 .canvas {
-	position: absolute;
-	object-fit: cover;
+	object-fit: contain;
 }
 
 .img {
diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue
index dc7344d7072e88a1ac27effffaf90c72d9673734..cda428a77ccfea7c58da610578320975760e4b75 100644
--- a/packages/frontend/src/components/MkInfo.vue
+++ b/packages/frontend/src/components/MkInfo.vue
@@ -21,6 +21,7 @@ const props = defineProps<{
 	background: var(--infoBg);
 	color: var(--infoFg);
 	border-radius: var(--radius);
+	white-space: pre-wrap;
 
 	&.warn {
 		background: var(--infoWarnBg);
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index 3e3d7354c1d6624e3c43ccd1c0aa3987edd41710..e48032d59926ce5e3baf2845d8973b66732509f1 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -1,12 +1,13 @@
 <template>
-<div class="matxzzsk">
-	<div class="label" @click="focus"><slot name="label"></slot></div>
-	<div class="input" :class="{ inline, disabled, focused }">
-		<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
+<div>
+	<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
+	<div :class="[$style.input, { [$style.inline]: inline, [$style.disabled]: disabled, [$style.focused]: focused }]">
+		<div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div>
 		<input
 			ref="inputEl"
 			v-model="v"
 			v-adaptive-border
+			:class="$style.inputCore"
 			:type="type"
 			:disabled="disabled"
 			:required="required"
@@ -25,11 +26,11 @@
 		<datalist v-if="datalist" :id="id">
 			<option v-for="data in datalist" :key="data" :value="data"/>
 		</datalist>
-		<div ref="suffixEl" class="suffix"><slot name="suffix"></slot></div>
+		<div ref="suffixEl" :class="$style.suffix"><slot name="suffix"></slot></div>
 	</div>
-	<div class="caption"><slot name="caption"></slot></div>
+	<div :class="$style.caption"><slot name="caption"></slot></div>
 
-	<MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+	<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
@@ -151,115 +152,110 @@ onMounted(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.matxzzsk {
-	> .label {
-		font-size: 0.85em;
-		padding: 0 0 8px 0;
-		user-select: none;
+<style lang="scss" module>
+.label {
+	font-size: 0.85em;
+	padding: 0 0 8px 0;
+	user-select: none;
 
-		&:empty {
-			display: none;
-		}
+	&:empty {
+		display: none;
 	}
+}
 
-	> .caption {
-		font-size: 0.85em;
-		padding: 8px 0 0 0;
-		color: var(--fgTransparentWeak);
+.caption {
+	font-size: 0.85em;
+	padding: 8px 0 0 0;
+	color: var(--fgTransparentWeak);
 
-		&:empty {
-			display: none;
-		}
+	&:empty {
+		display: none;
 	}
+}
 
-	> .input {
-		position: relative;
-
-		> input {
-			appearance: none;
-			-webkit-appearance: none;
-			display: block;
-			height: v-bind("height + 'px'");
-			width: 100%;
-			margin: 0;
-			padding: 0 12px;
-			font: inherit;
-			font-weight: normal;
-			font-size: 1em;
-			color: var(--fg);
-			background: var(--panel);
-			border: solid 1px var(--panel);
-			border-radius: 6px;
-			outline: none;
-			box-shadow: none;
-			box-sizing: border-box;
-			transition: border-color 0.1s ease-out;
-
-			&:hover {
-				border-color: var(--inputBorderHover) !important;
-			}
-		}
-
-		> .prefix,
-		> .suffix {
-			display: flex;
-			align-items: center;
-			position: absolute;
-			z-index: 1;
-			top: 0;
-			padding: 0 12px;
-			font-size: 1em;
-			height: v-bind("height + 'px'");
-			pointer-events: none;
-
-			&:empty {
-				display: none;
-			}
-
-			> * {
-				display: inline-block;
-				min-width: 16px;
-				max-width: 150px;
-				overflow: hidden;
-				white-space: nowrap;
-				text-overflow: ellipsis;
-			}
-		}
+.input {
+	position: relative;
 
-		> .prefix {
-			left: 0;
-			padding-right: 6px;
-		}
+	&.inline {
+		display: inline-block;
+		margin: 0;
+	}
 
-		> .suffix {
-			right: 0;
-			padding-left: 6px;
+	&.focused {
+		> .inputCore {
+			border-color: var(--accent) !important;
+			//box-shadow: 0 0 0 4px var(--focus);
 		}
+	}
 
-		&.inline {
-			display: inline-block;
-			margin: 0;
-		}
+	&.disabled {
+		opacity: 0.7;
 
-		&.focused {
-			> input {
-				border-color: var(--accent) !important;
-				//box-shadow: 0 0 0 4px var(--focus);
-			}
+		&,
+		> .inputCore {
+			cursor: not-allowed !important;
 		}
+	}
+}
 
-		&.disabled {
-			opacity: 0.7;
-
-			&, * {
-				cursor: not-allowed !important;
-			}
-		}
+.inputCore {
+	appearance: none;
+	-webkit-appearance: none;
+	display: block;
+	height: v-bind("height + 'px'");
+	width: 100%;
+	margin: 0;
+	padding: 0 12px;
+	font: inherit;
+	font-weight: normal;
+	font-size: 1em;
+	color: var(--fg);
+	background: var(--panel);
+	border: solid 1px var(--panel);
+	border-radius: 6px;
+	outline: none;
+	box-shadow: none;
+	box-sizing: border-box;
+	transition: border-color 0.1s ease-out;
+
+	&:hover {
+		border-color: var(--inputBorderHover) !important;
 	}
+}
 
-	> .save {
-		margin: 8px 0 0 0;
+.prefix,
+.suffix {
+	display: flex;
+	align-items: center;
+	position: absolute;
+	z-index: 1;
+	top: 0;
+	padding: 0 12px;
+	font-size: 1em;
+	height: v-bind("height + 'px'");
+	min-width: 16px;
+	max-width: 150px;
+	overflow: hidden;
+	white-space: nowrap;
+	text-overflow: ellipsis;
+	box-sizing: border-box;
+	pointer-events: none;
+
+	&:empty {
+		display: none;
 	}
 }
+
+.prefix {
+	left: 0;
+	padding-right: 6px;
+}
+
+.suffix {
+	right: 0;
+	padding-left: 6px;
+}
+.save {
+	margin: 8px 0 0 0;
+}
 </style>
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index a4065dcd0729a442c637cb37afc011c04719ba07..42dc9e79ff55c154d0eb9d6408d0d3712f4b082f 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -1,9 +1,10 @@
 <template>
 <div v-if="hide" :class="$style.hidden" @click="hide = false">
-	<ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/>
+	<ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment" :width="image.properties.width" :height="image.properties.height" :force-blurhash="defaultStore.state.enableDataSaverMode"/>
 	<div :class="$style.hiddenText">
 		<div :class="$style.hiddenTextWrapper">
-			<b style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}</b>
+			<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
+			<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
 			<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
 		</div>
 	</div>
@@ -14,13 +15,15 @@
 		:href="image.url"
 		:title="image.name"
 	>
-		<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :cover="false"/>
+		<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :width="image.properties.width" :height="image.properties.height" :cover="false"/>
 	</a>
 	<div :class="$style.indicators">
 		<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
 		<div v-if="image.comment" :class="$style.indicator">ALT</div>
+		<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);">NSFW</div>
 	</div>
 	<button v-tooltip="i18n.ts.hide" :class="$style.hide" class="_button" @click="hide = true"><i class="ti ti-eye-off"></i></button>
+	<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots"></i></button>
 </div>
 </template>
 
@@ -28,9 +31,12 @@
 import { watch } from 'vue';
 import * as misskey from 'misskey-js';
 import { getStaticImageUrl } from '@/scripts/media-proxy';
+import bytes from '@/filters/bytes';
 import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
+import * as os from '@/os';
+import { iAmModerator } from '@/account';
 
 const props = defineProps<{
 	image: misskey.entities.DriveFile;
@@ -38,21 +44,33 @@ const props = defineProps<{
 }>();
 
 let hide = $ref(true);
-let darkMode = $ref(defaultStore.state.darkMode);
+let darkMode: boolean = $ref(defaultStore.state.darkMode);
 
-const url = (props.raw || defaultStore.state.loadRawImages)
+const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
 	? props.image.url
 	: defaultStore.state.disableShowingAnimatedImages
 		? getStaticImageUrl(props.image.url)
-		: props.image.thumbnailUrl;
+		: props.image.thumbnailUrl,
+);
 
 // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
 watch(() => props.image, () => {
-	hide = (defaultStore.state.nsfw === 'force') ? true : props.image.isSensitive && (defaultStore.state.nsfw !== 'ignore');
+	hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
 }, {
 	deep: true,
 	immediate: true,
 });
+
+function showMenu(ev: MouseEvent) {
+	os.popupMenu([...(iAmModerator ? [{
+		text: i18n.ts.markAsSensitive,
+		icon: 'ti ti-eye-off',
+		action: () => {
+			os.apiWithDialog('drive/files/update', { fileId: props.image.id, isSensitive: true });
+		},
+	}] : [])], ev.currentTarget ?? ev.target);
+}
+
 </script>
 
 <style lang="scss" module>
@@ -102,6 +120,21 @@ watch(() => props.image, () => {
 	right: 12px;
 }
 
+.menu {
+	display: block;
+	position: absolute;
+	border-radius: 6px;
+	background-color: rgba(0, 0, 0, 0.3);
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+	color: #fff;
+	font-size: 0.8em;
+	padding: 6px 8px;
+	text-align: center;
+	bottom: 12px;
+	right: 12px;
+}
+
 .imageContainer {
 	display: block;
 	cursor: zoom-in;
@@ -132,6 +165,7 @@ watch(() => props.image, () => {
 	color: var(--accentLighten);
 	display: inline-block;
 	font-weight: bold;
-	padding: 0 6px;
+	font-size: 12px;
+	padding: 2px 6px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index d36cc2d26b5b480b97fb57308f463e5f0f0ebad4..e456ff3eec76c395ee4833bd0d3e01a4d2c16900 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -2,10 +2,16 @@
 <div>
 	<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/>
 	<div v-if="mediaList.filter(media => previewable(media)).length > 0" :class="$style.container">
-		<div ref="gallery" :class="[$style.medias, count <= 4 ? $style['n' + count] : $style.nMany]">
+		<div
+			ref="gallery"
+			:class="[
+				$style.medias,
+				count <= 4 ? $style['n' + count] : $style.nMany,
+			]"
+		>
 			<template v-for="media in mediaList.filter(media => previewable(media))">
-				<XVideo v-if="media.type.startsWith('video')" :key="media.id" :class="$style.media" :video="media"/>
-				<XImage v-else-if="media.type.startsWith('image')" :key="media.id" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/>
+				<XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.media" :video="media"/>
+				<XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/>
 			</template>
 		</div>
 	</div>
@@ -13,7 +19,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, useCssModule } from 'vue';
+import { onMounted, ref, useCssModule, watch } from 'vue';
 import * as misskey from 'misskey-js';
 import PhotoSwipeLightbox from 'photoswipe/lightbox';
 import PhotoSwipe from 'photoswipe';
@@ -23,6 +29,7 @@ import XImage from '@/components/MkMediaImage.vue';
 import XVideo from '@/components/MkMediaVideo.vue';
 import * as os from '@/os';
 import { FILE_TYPE_BROWSERSAFE } from '@/const';
+import { defaultStore } from '@/store';
 
 const props = defineProps<{
 	mediaList: misskey.entities.DriveFile[];
@@ -31,7 +38,7 @@ const props = defineProps<{
 
 const $style = useCssModule();
 
-const gallery = ref(null);
+const gallery = ref<HTMLDivElement>();
 const pswpZIndex = os.claimZIndex('middle');
 document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
 const count = $computed(() => props.mediaList.filter(media => previewable(media)).length);
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index e02a7af09ead97c273bd2d2f4940a65049e74d45..a4b76300e6ad71274380aa63159dba087a148ae4 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -1,7 +1,9 @@
 <template>
 <div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false">
+	<!-- 【注意】dataSaverMode が有効になっている際には、hide が false になるまでサムネイルや動画を読み込まないようにすること -->
 	<div>
-		<b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}</b>
+		<b v-if="video.isSensitive"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
+		<b v-else><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b>
 		<span>{{ i18n.ts.clickToShow }}</span>
 	</div>
 </div>
@@ -25,6 +27,7 @@
 <script lang="ts" setup>
 import { ref } from 'vue';
 import * as misskey from 'misskey-js';
+import bytes from '@/filters/bytes';
 import VuePlyr from 'vue-plyr';
 import { defaultStore } from '@/store';
 import 'vue-plyr/dist/vue-plyr.css';
@@ -34,7 +37,7 @@ const props = defineProps<{
 	video: misskey.entities.DriveFile;
 }>();
 
-const hide = ref((defaultStore.state.nsfw === 'force') ? true : props.video.isSensitive && (defaultStore.state.nsfw !== 'ignore'));
+const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index 852c72f6ffc47110a0c68e0f9d9829e2b9922b0a..99df9e8150863f65a8f488ded028df5493e1814b 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -404,16 +404,10 @@ defineExpose({
 			right: 0;
 			margin: auto;
 			padding: 32px;
-			// TODO: mask-imageはiOSだとやたら重い。なんとかしたい
-			-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
-			mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 32px, rgba(0,0,0,1) calc(100% - 32px), rgba(0,0,0,0) 100%);
-			overflow: auto;
 			display: flex;
 
 			@media (max-width: 500px) {
 				padding: 16px;
-				-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
-				mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 16px, rgba(0,0,0,1) calc(100% - 16px), rgba(0,0,0,0) 100%);
 			}
 		}
 	}
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index dd115246ff101614ea772f591b6095d31d20a848..1c942cfd0d5f34814a98e2408b5e29e5e65e18bb 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -1,12 +1,12 @@
 <template>
 <MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
-	<div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) : (height ? `min(${height}px, 100%)` : '100%') }" @keydown="onKeydown">
+	<div ref="rootEl" class="ebkgoccj" :style="{ width: `${width}px`, height: height ? `${height}px` : null }" @keydown="onKeydown">
 		<div ref="headerEl" class="header">
 			<button v-if="withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
 			<span class="title">
 				<slot name="header"></slot>
 			</span>
-			<button v-if="!withOkButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
+			<button v-if="!withOkButton" class="_button" data-cy-modal-window-close @click="$emit('close')"><i class="ti ti-x"></i></button>
 			<button v-if="withOkButton" class="_button" :disabled="okButtonDisabled" @click="$emit('ok')"><i class="ti ti-check"></i></button>
 		</div>
 		<div class="body">
@@ -25,13 +25,11 @@ const props = withDefaults(defineProps<{
 	okButtonDisabled: boolean;
 	width: number;
 	height: number | null;
-	scroll: boolean;
 }>(), {
 	withOkButton: false,
 	okButtonDisabled: false,
 	width: 400,
 	height: null,
-	scroll: true,
 });
 
 const emit = defineEmits<{
@@ -86,11 +84,11 @@ defineExpose({
 <style lang="scss" scoped>
 .ebkgoccj {
 	margin: auto;
+	max-height: 100%;
 	overflow: hidden;
 	display: flex;
 	flex-direction: column;
 	contain: content;
-	container-type: inline-size;
 	border-radius: var(--radius);
 
 	--root-margin: 24px;
@@ -143,6 +141,7 @@ defineExpose({
 		flex: 1;
 		overflow: auto;
 		background: var(--panel);
+		container-type: size;
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 36ec778a14b427719d31985864794e053de03197..d95f8de311a9568adb1904c79f96d9f43312b296 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -12,6 +12,7 @@
 	<!--<div v-if="appearNote._prId_" class="tip"><i class="ti ti-speakerphone"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
 	<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
 	<div v-if="isRenote" :class="$style.renote">
+		<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
 		<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
 		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
 		<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
@@ -40,6 +41,7 @@
 		<Mfm :text="getNoteSummary(appearNote)" :plain="true" :nowrap="true" :author="appearNote.user" :class="$style.collapsedRenoteTargetText" @click="renoteCollapsed = false"/>
 	</div>
 	<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
+		<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
 		<MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/>
 		<div :class="$style.main">
 			<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
@@ -162,6 +164,7 @@ import { claimAchievement } from '@/scripts/achievements';
 import { getNoteSummary } from '@/scripts/get-note-summary';
 import { MenuItem } from '@/types/menu';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import { showMovedDialog } from '@/scripts/show-moved-dialog';
 
 const props = defineProps<{
 	note: misskey.entities.Note;
@@ -255,6 +258,7 @@ useTooltip(renoteButton, async (showing) => {
 
 function renote(viaKeyboard = false) {
 	pleaseLogin();
+	showMovedDialog();
 
 	let items = [] as MenuItem[];
 
@@ -335,6 +339,7 @@ function reply(viaKeyboard = false): void {
 
 function react(viaKeyboard = false): void {
 	pleaseLogin();
+	showMovedDialog();
 	if (appearNote.reactionAcceptance === 'likeOnly') {
 		os.api('notes/reactions/create', {
 			noteId: appearNote.id,
@@ -401,6 +406,7 @@ async function clip() {
 
 function showRenoteMenu(viaKeyboard = false): void {
 	if (!isMyRenote) return;
+	pleaseLogin();
 	os.popupMenu([{
 		text: i18n.ts.unrenote,
 		icon: 'ti ti-trash',
@@ -484,6 +490,11 @@ function showReactions(): void {
 		}
 	}
 
+	.footer {
+		position: relative;
+		z-index: 1;
+	}
+
 	&:hover > .article > .main > .footer > .footerButton {
 		opacity: 1;
 	}
@@ -537,6 +548,7 @@ function showReactions(): void {
 }
 
 .renote {
+	position: relative;
 	display: flex;
 	align-items: center;
 	padding: 16px 32px 8px 32px;
@@ -547,6 +559,10 @@ function showReactions(): void {
 	& + .article {
 		padding-top: 8px;
 	}
+
+	> .colorBar {
+		height: calc(100% - 6px);
+	}
 }
 
 .renoteAvatar {
@@ -618,6 +634,16 @@ function showReactions(): void {
 	padding: 28px 32px;
 }
 
+.colorBar {
+	position: absolute;
+	top: 8px;
+	left: 8px;
+	width: 5px;
+	height: calc(100% - 16px);
+	border-radius: 999px;
+	pointer-events: none;
+}
+
 .avatar {
 	flex-shrink: 0;
 	display: block !important;
@@ -669,6 +695,7 @@ function showReactions(): void {
 	position: absolute;
 	bottom: 0;
 	left: 0;
+	z-index: 2;
 	width: 100%;
 	height: 64px;
 	background: linear-gradient(0deg, var(--panel), var(--X15));
@@ -833,6 +860,13 @@ function showReactions(): void {
 			}
 		}
 	}
+
+	.colorBar {
+		top: 6px;
+		left: 6px;
+		width: 4px;
+		height: calc(100% - 12px);
+	}
 }
 
 @container (max-width: 300px) {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index b9ab3668507eaa907d98a64a17b3fb715f0f01df..0d6d329d9813087523b6c4427e33a33d2ce72b56 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -166,6 +166,7 @@ import { useTooltip } from '@/scripts/use-tooltip';
 import { claimAchievement } from '@/scripts/achievements';
 import { MenuItem } from '@/types/menu';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import { showMovedDialog } from '@/scripts/show-moved-dialog';
 
 const props = defineProps<{
 	note: misskey.entities.Note;
@@ -248,6 +249,7 @@ useTooltip(renoteButton, async (showing) => {
 
 function renote(viaKeyboard = false) {
 	pleaseLogin();
+	showMovedDialog();
 
 	let items = [] as MenuItem[];
 
@@ -318,6 +320,7 @@ function renote(viaKeyboard = false) {
 
 function reply(viaKeyboard = false): void {
 	pleaseLogin();
+	showMovedDialog();
 	os.post({
 		reply: appearNote,
 		animation: !viaKeyboard,
@@ -328,6 +331,7 @@ function reply(viaKeyboard = false): void {
 
 function react(viaKeyboard = false): void {
 	pleaseLogin();
+	showMovedDialog();
 	if (appearNote.reactionAcceptance === 'likeOnly') {
 		os.api('notes/reactions/create', {
 			noteId: appearNote.id,
@@ -394,6 +398,7 @@ async function clip() {
 
 function showRenoteMenu(viaKeyboard = false): void {
 	if (!isMyRenote) return;
+	pleaseLogin();
 	os.popupMenu([{
 		text: i18n.ts.unrenote,
 		icon: 'ti ti-trash',
diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue
index e7d4a5472af281d72c737d37b23ee9f6fa07dd67..303417dae846e80998a4b361383b42f4fc94903d 100644
--- a/packages/frontend/src/components/MkNumberDiff.vue
+++ b/packages/frontend/src/components/MkNumberDiff.vue
@@ -1,47 +1,32 @@
 <template>
-<span class="ceaaebcd" :class="{ isPlus, isMinus, isZero }">
+<span class="ceaaebcd" :class="{ [$style.isPlus]: isPlus, [$style.isMinus]: isMinus, [$style.isZero]: isZero }">
 	<slot name="before"></slot>{{ isPlus ? '+' : '' }}{{ number(value) }}<slot name="after"></slot>
 </span>
 </template>
 
-<script lang="ts">
-import { computed, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { computed } from 'vue';
 import number from '@/filters/number';
 
-export default defineComponent({
-	props: {
-		value: {
-			type: Number,
-			required: true,
-		},
-	},
+const props = defineProps<{
+	value: number;
+}>();
 
-	setup(props) {
-		const isPlus = computed(() => props.value > 0);
-		const isMinus = computed(() => props.value < 0);
-		const isZero = computed(() => props.value === 0);
-		return {
-			isPlus,
-			isMinus,
-			isZero,
-			number,
-		};
-	},
-});
+const isPlus = computed(() => props.value > 0);
+const isMinus = computed(() => props.value < 0);
+const isZero = computed(() => props.value === 0);
 </script>
 
-<style lang="scss" scoped>
-.ceaaebcd {
-	&.isPlus {
-		color: var(--success);
-	}
+<style lang="scss" module>
+.isPlus {
+	color: var(--success);
+}
 
-	&.isMinus {
-		color: var(--error);
-	}
+.isMinus {
+	color: var(--error);
+}
 
-	&.isZero {
-		opacity: 0.5;
-	}
+.isZero {
+	opacity: 0.5;
 }
 </style>
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index 0f148022bf0f9893d6f6b21ab88a23accfe2b677..e2d68d12c348fa0f1f7717d957318c784b87be2e 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -17,7 +17,7 @@ const props = withDefaults(defineProps<{
 	maxHeight: 200,
 });
 
-let content = $ref<HTMLElement>();
+let content = $shallowRef<HTMLElement>();
 let omitted = $ref(false);
 let ignoreOmit = $ref(false);
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 42a3748d9aa50472ad47fea3fbc54102d5df6f9f..c65cb7d6e5d247c0ad61e1459bb8c96ae411b1a5 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -247,6 +247,10 @@ watch($$(text), () => {
 	checkMissingMention();
 }, { immediate: true });
 
+watch($$(visibility), () => {
+	checkMissingMention();
+}, { immediate: true });
+
 watch($$(visibleUsers), () => {
 	checkMissingMention();
 }, {
@@ -900,27 +904,28 @@ defineExpose({
 }
 
 .headerLeft {
-	display: grid;
-	grid-template-columns: repeat(2, minmax(36px, 50px));
-	grid-template-rows: minmax(40px, 100%);
+	display: flex;
+	flex: 0 1 100px;
 }
 
 .cancel {
 	padding: 0;
 	font-size: 1em;
 	height: 100%;
+	flex: 0 1 50px;
 }
 
 .account {
 	height: 100%;
 	display: inline-flex;
 	vertical-align: bottom;
+	flex: 0 1 50px;
 }
 
 .avatar {
 	width: 28px;
 	height: 28px;
-	margin: auto 0;
+	margin: auto;
 }
 
 .headerRight {
diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue
index fcf454c77a93f08161a660f3fba39f5502093967..5db2f5ee6d79a4fbcb21201187ebb16a07d5cfb2 100644
--- a/packages/frontend/src/components/MkRadio.vue
+++ b/packages/frontend/src/components/MkRadio.vue
@@ -24,7 +24,7 @@ import { } from 'vue';
 const props = defineProps<{
 	modelValue: any;
 	value: any;
-	disabled: boolean;
+	disabled?: boolean;
 }>();
 
 const emit = defineEmits<{
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index 8590ccf9ae2adbedd6a5b68f22c91a2243055eef..e2240fb4e13500bf02bf73b16801e03ba2ba3fe0 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -1,5 +1,5 @@
 <script lang="ts">
-import { defineComponent, h } from 'vue';
+import { VNode, defineComponent, h } from 'vue';
 import MkRadio from './MkRadio.vue';
 
 export default defineComponent({
@@ -22,31 +22,33 @@ export default defineComponent({
 		},
 	},
 	render() {
+		console.log(this.$slots, this.$slots.label && this.$slots.label());
+		if (!this.$slots.default) return null;
 		let options = this.$slots.default();
 		const label = this.$slots.label && this.$slots.label();
 		const caption = this.$slots.caption && this.$slots.caption();
 
 		// なぜかFragmentになることがあるため
-		if (options.length === 1 && options[0].props == null) options = options[0].children;
+		if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[];
 
 		return h('div', {
 			class: 'novjtcto',
 		}, [
 			...(label ? [h('div', {
 				class: 'label',
-			}, [label])] : []),
+			}, label)] : []),
 			h('div', {
 				class: 'body',
 			}, options.map(option => h(MkRadio, {
 				key: option.key,
-				value: option.props.value,
+				value: option.props?.value,
 				modelValue: this.value,
 				'onUpdate:modelValue': value => this.value = value,
-			}, option.children)),
+			}, () => option.children)),
 			),
 			...(caption ? [h('div', {
 				class: 'caption',
-			}, [caption])] : []),
+			}, caption)] : []),
 		]);
 	},
 });
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue
index a1ee6367a0da172da6ab020e82e0381ac64bba15..eaa134df25c0e91a6c454d2a99cedd91aecbc5a6 100644
--- a/packages/frontend/src/components/MkRange.vue
+++ b/packages/frontend/src/components/MkRange.vue
@@ -17,7 +17,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, watch, shallowRef } from 'vue';
 import * as os from '@/os';
 
 const props = withDefaults(defineProps<{
@@ -39,8 +39,8 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', value: number): void;
 }>();
 
-const containerEl = ref<HTMLElement>();
-const thumbEl = ref<HTMLElement>();
+const containerEl = shallowRef<HTMLElement>();
+const thumbEl = shallowRef<HTMLElement>();
 
 const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
 const steppedRawValue = computed(() => {
diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue
index 1506e24ce885ed1dc23a5a9ba633207b4e01d079..0c0cc36692ead8642e3757216721e0812e432845 100644
--- a/packages/frontend/src/components/MkReactedUsersDialog.vue
+++ b/packages/frontend/src/components/MkReactedUsersDialog.vue
@@ -6,7 +6,7 @@
 	@close="dialog.close()"
 	@closed="emit('closed')"
 >
-	<template #header>{{ i18n.ts.reactions }}</template>
+	<template #header>{{ i18n.ts.reactionsList }}</template>
 
 	<MkSpacer :margin-min="20" :margin-max="28">
 		<div v-if="note" class="_gaps">
@@ -21,7 +21,7 @@
 						<span style="margin-left: 4px;">{{ note.reactions[reaction] }}</span>
 					</button>
 				</div>
-				<MkA v-for="user in users" :key="user.id" :to="userPage(user)">
+				<MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()">
 					<MkUserCardMini :user="user" :with-chart="false"/>
 				</MkA>
 			</template>
diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue
index b4210be91175f9c4b1631e0503ce979aebb2ddd6..f5e611c62a08db711dde8c3863fa70ee8d61c734 100644
--- a/packages/frontend/src/components/MkReactionsViewer.details.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.details.vue
@@ -10,7 +10,7 @@
 				<MkAvatar :class="$style.avatar" :user="u"/>
 				<MkUserName :user="u" :nowrap="true"/>
 			</div>
-			<div v-if="users.length > 10">+{{ count - 10 }}</div>
+			<div v-if="users.length > 10" :class="$style.more">+{{ count - 10 }}</div>
 		</div>
 	</div>
 </MkTooltip>
@@ -50,7 +50,9 @@ function getReactionName(reaction: string): string {
 
 .reaction {
 	max-width: 100px;
+	padding-right: 10px;
 	text-align: center;
+	border-right: solid 0.5px var(--divider);
 }
 
 .reactionIcon {
@@ -66,25 +68,20 @@ function getReactionName(reaction: string): string {
 }
 
 .users {
+	contain: content;
 	flex: 1;
 	min-width: 0;
+	margin: -4px 14px 0 10px;
 	font-size: 0.95em;
-	border-left: solid 0.5px var(--divider);
-	padding-left: 10px;
-	margin-left: 10px;
-	margin-right: 14px;
 	text-align: left;
 }
 
 .user {
 	line-height: 24px;
+	padding-top: 4px;
 	white-space: nowrap;
 	overflow: hidden;
 	text-overflow: ellipsis;
-
-	&:not(:last-child) {
-		margin-bottom: 3px;
-	}
 }
 
 .avatar {
@@ -92,4 +89,8 @@ function getReactionName(reaction: string): string {
 	height: 24px;
 	margin-right: 3px;
 }
+
+.more {
+	padding-top: 4px;
+}
 </style>
diff --git a/packages/frontend/src/components/MkRenotedUsersDialog.vue b/packages/frontend/src/components/MkRenotedUsersDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..56025535f184ed989884e452f4c5b0452df923d2
--- /dev/null
+++ b/packages/frontend/src/components/MkRenotedUsersDialog.vue
@@ -0,0 +1,65 @@
+<template>
+<MkModalWindow
+	ref="dialog"
+	:width="400"
+	:height="450"
+	@close="dialog.close()"
+	@closed="emit('closed')"
+>
+	<template #header>{{ i18n.ts.renotesList }}</template>
+
+	<MkSpacer :margin-min="20" :margin-max="28">
+		<div v-if="renotes" class="_gaps">
+			<div v-if="renotes.length === 0" class="_fullinfo">
+				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+				<div>{{ i18n.ts.nothing }}</div>
+			</div>
+			<template v-else>
+				<MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()">
+					<MkUserCardMini :user="user" :with-chart="false"/>
+				</MkA>
+			</template>
+		</div>
+		<div v-else>
+			<MkLoading/>
+		</div>
+	</MkSpacer>
+</MkModalWindow>
+</template>
+
+<script lang="ts" setup>
+import { onMounted } from 'vue';
+import * as misskey from 'misskey-js';
+import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import { userPage } from '@/filters/user';
+import { i18n } from '@/i18n';
+import * as os from '@/os';
+
+const emit = defineEmits<{
+	(ev: 'closed'): void,
+}>();
+
+const props = defineProps<{
+	noteId: misskey.entities.Note['id'];
+}>();
+
+const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
+
+let note = $ref<misskey.entities.Note>();
+let renotes = $ref();
+let users = $ref();
+
+onMounted(async () => {
+	const res = await os.api('notes/renotes', {
+		noteId: props.noteId,
+		limit: 30,
+	});
+
+	renotes = res;
+	users = res.map(x => x.user);
+});
+</script>
+
+<style lang="scss" module>
+</style>
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 85c009f746014a858e2097fc10ef328d65ae03c7..f33f68cab7231d009a2eb593833a574d78a9159d 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -44,7 +44,13 @@ async function renderChart() {
 
 	const data = [];
 	for (const record of raw) {
-		let i = 0;
+		data.push({
+			x: 0,
+			y: record.createdAt,
+			v: record.users,
+		});
+
+		let i = 1;
 		for (const date of Object.keys(record.data).sort((a, b) => new Date(a).getTime() - new Date(b).getTime())) {
 			data.push({
 				x: i,
@@ -61,8 +67,14 @@ async function renderChart() {
 
 	const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
 
-	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
-	const max = raw.map(x => x.users).slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
+	const getYYYYMMDD = (date: Date) => {
+		const y = date.getFullYear().toString().padStart(2, '0');
+		const m = (date.getMonth() + 1).toString().padStart(2, '0');
+		const d = date.getDate().toString().padStart(2, '0');
+		return `${y}/${m}/${d}`;
+	};
+
+	const max = (createdAt: string) => raw.find(x => x.createdAt === createdAt)!.users;
 
 	const marginEachCell = 12;
 
@@ -78,7 +90,7 @@ async function renderChart() {
 				borderRadius: 3,
 				backgroundColor(c) {
 					const value = c.dataset.data[c.dataIndex].v;
-					const a = value / max;
+					const a = value / max(c.dataset.data[c.dataIndex].y);
 					return alpha(color, a);
 				},
 				fill: true,
@@ -115,7 +127,7 @@ async function renderChart() {
 						maxRotation: 0,
 						autoSkipPadding: 0,
 						autoSkip: false,
-						callback: (value, index, values) => value + 1,
+						callback: (value, index, values) => value,
 					},
 				},
 				y: {
@@ -150,11 +162,11 @@ async function renderChart() {
 					callbacks: {
 						title(context) {
 							const v = context[0].dataset.data[context[0].dataIndex];
-							return v.d;
+							return getYYYYMMDD(new Date(new Date(v.y).getTime() + (v.x * 86400000)));
 						},
 						label(context) {
 							const v = context.dataset.data[context.dataIndex];
-							return ['Active: ' + v.v];
+							return [`Active: ${v.v} (${Math.round((v.v / max(v.y)) * 100)}%)`];
 						},
 					},
 					//mode: 'index',
diff --git a/packages/frontend/src/components/MkSample.vue b/packages/frontend/src/components/MkSample.vue
index 7a3bc208880d8e5f261c1eb8ed1658222aa3704f..922b862b47020b7ed2e07884204e21759d450302 100644
--- a/packages/frontend/src/components/MkSample.vue
+++ b/packages/frontend/src/components/MkSample.vue
@@ -87,7 +87,7 @@ export default defineComponent({
 		},
 
 		async openDrive() {
-			os.selectDriveFile();
+			os.selectDriveFile(false);
 		},
 
 		async selectUser() {
diff --git a/packages/frontend/src/components/MkSignup.vue b/packages/frontend/src/components/MkSignup.vue
deleted file mode 100644
index 30279148f80063e2f034fabd5a76100ae2bd9251..0000000000000000000000000000000000000000
--- a/packages/frontend/src/components/MkSignup.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-<template>
-<form class="qlvuhzng _gaps_m" autocomplete="new-password" @submit.prevent="onSubmit">
-	<MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required>
-		<template #label>{{ i18n.ts.invitationCode }}</template>
-		<template #prefix><i class="ti ti-key"></i></template>
-	</MkInput>
-	<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:model-value="onChangeUsername">
-		<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-question-circle"></i></div></template>
-		<template #prefix>@</template>
-		<template #suffix>@{{ host }}</template>
-		<template #caption>
-			<div><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.cannotBeChangedLater }}</div>
-			<span v-if="usernameState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span>
-			<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
-			<span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
-			<span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
-			<span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
-			<span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span>
-			<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span>
-		</template>
-	</MkInput>
-	<MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:model-value="onChangeEmail">
-		<template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ti ti-question-circle"></i></div></template>
-		<template #prefix><i class="ti ti-mail"></i></template>
-		<template #caption>
-			<span v-if="emailState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span>
-			<span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
-			<span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span>
-			<span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span>
-			<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
-			<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
-			<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
-			<span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
-			<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
-		</template>
-	</MkInput>
-	<MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:model-value="onChangePassword">
-		<template #label>{{ i18n.ts.password }}</template>
-		<template #prefix><i class="ti ti-lock"></i></template>
-		<template #caption>
-			<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span>
-			<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span>
-			<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span>
-		</template>
-	</MkInput>
-	<MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:model-value="onChangePasswordRetype">
-		<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
-		<template #prefix><i class="ti ti-lock"></i></template>
-		<template #caption>
-			<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span>
-			<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span>
-		</template>
-	</MkInput>
-	<MkSwitch v-model="ToSAgreement" class="tou">
-		<template #label>{{ i18n.ts.agreeBelow }}</template>
-	</MkSwitch>
-	<ul style="margin: 0; padding-left: 2em;">
-		<li v-if="instance.tosUrl"><a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.tos }}</a></li>
-		<li><a href="https://misskey-hub.net/docs/notes.html" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }}</a></li>
-	</ul>
-	<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
-	<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
-	<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" class="captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
-	<MkButton type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ i18n.ts.start }}</MkButton>
-</form>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import getPasswordStrength from 'syuilo-password-strength';
-import { toUnicode } from 'punycode/';
-import MkButton from './MkButton.vue';
-import MkInput from './MkInput.vue';
-import MkSwitch from './MkSwitch.vue';
-import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
-import * as config from '@/config';
-import * as os from '@/os';
-import { login } from '@/account';
-import { instance } from '@/instance';
-import { i18n } from '@/i18n';
-
-const props = withDefaults(defineProps<{
-	autoSet?: boolean;
-}>(), {
-	autoSet: false,
-});
-
-const emit = defineEmits<{
-	(ev: 'signup', user: Record<string, any>): void;
-	(ev: 'signupEmailPending'): void;
-}>();
-
-const host = toUnicode(config.host);
-
-let hcaptcha = $ref<Captcha | undefined>();
-let recaptcha = $ref<Captcha | undefined>();
-let turnstile = $ref<Captcha | undefined>();
-
-let username: string = $ref('');
-let password: string = $ref('');
-let retypedPassword: string = $ref('');
-let invitationCode: string = $ref('');
-let email = $ref('');
-let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null);
-let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null);
-let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
-let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
-let submitting: boolean = $ref(false);
-let ToSAgreement: boolean = $ref(false);
-let hCaptchaResponse = $ref(null);
-let reCaptchaResponse = $ref(null);
-let turnstileResponse = $ref(null);
-let usernameAbortController: null | AbortController = $ref(null);
-let emailAbortController: null | AbortController = $ref(null);
-
-const shouldDisableSubmitting = $computed((): boolean => {
-	return submitting ||
-		instance.tosUrl && !ToSAgreement ||
-		instance.enableHcaptcha && !hCaptchaResponse ||
-		instance.enableRecaptcha && !reCaptchaResponse ||
-		instance.enableTurnstile && !turnstileResponse ||
-		instance.emailRequiredForSignup && emailState !== 'ok' ||
-		usernameState !== 'ok' ||
-		passwordRetypeState !== 'match';
-});
-
-function onChangeUsername(): void {
-	if (username === '') {
-		usernameState = null;
-		return;
-	}
-
-	{
-		const err =
-			!username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
-			username.length < 1 ? 'min-range' :
-			username.length > 20 ? 'max-range' :
-			null;
-
-		if (err) {
-			usernameState = err;
-			return;
-		}
-	}
-
-	if (usernameAbortController != null) {
-		usernameAbortController.abort();
-	}
-	usernameState = 'wait';
-	usernameAbortController = new AbortController();
-
-	os.api('username/available', {
-		username,
-	}, undefined, usernameAbortController.signal).then(result => {
-		usernameState = result.available ? 'ok' : 'unavailable';
-	}).catch((err) => {
-		if (err.name !== 'AbortError') {
-			usernameState = 'error';
-		}
-	});
-}
-
-function onChangeEmail(): void {
-	if (email === '') {
-		emailState = null;
-		return;
-	}
-
-	if (emailAbortController != null) {
-		emailAbortController.abort();
-	}
-	emailState = 'wait';
-	emailAbortController = new AbortController();
-
-	os.api('email-address/available', {
-		emailAddress: email,
-	}, undefined, emailAbortController.signal).then(result => {
-		emailState = result.available ? 'ok' :
-			result.reason === 'used' ? 'unavailable:used' :
-			result.reason === 'format' ? 'unavailable:format' :
-			result.reason === 'disposable' ? 'unavailable:disposable' :
-			result.reason === 'mx' ? 'unavailable:mx' :
-			result.reason === 'smtp' ? 'unavailable:smtp' :
-			'unavailable';
-	}).catch((err) => {
-		if (err.name !== 'AbortError') {
-			emailState = 'error';
-		}
-	});
-}
-
-function onChangePassword(): void {
-	if (password === '') {
-		passwordStrength = '';
-		return;
-	}
-
-	const strength = getPasswordStrength(password);
-	passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
-}
-
-function onChangePasswordRetype(): void {
-	if (retypedPassword === '') {
-		passwordRetypeState = null;
-		return;
-	}
-
-	passwordRetypeState = password === retypedPassword ? 'match' : 'not-match';
-}
-
-async function onSubmit(): Promise<void> {
-	if (submitting) return;
-	submitting = true;
-
-	try {
-		await os.api('signup', {
-			username,
-			password,
-			emailAddress: email,
-			invitationCode,
-			'hcaptcha-response': hCaptchaResponse,
-			'g-recaptcha-response': reCaptchaResponse,
-			'turnstile-response': turnstileResponse,
-		});
-		if (instance.emailRequiredForSignup) {
-			os.alert({
-				type: 'success',
-				title: i18n.ts._signup.almostThere,
-				text: i18n.t('_signup.emailSent', { email }),
-			});
-			emit('signupEmailPending');
-		} else {
-			const res = await os.api('signin', {
-				username,
-				password,
-			});
-			emit('signup', res);
-
-			if (props.autoSet) {
-				return login(res.i);
-			}
-		}
-	} catch {
-		submitting = false;
-		hcaptcha?.reset?.();
-		recaptcha?.reset?.();
-		turnstile?.reset?.();
-
-		os.alert({
-			type: 'error',
-			text: i18n.ts.somethingHappened,
-		});
-	}
-}
-</script>
-
-<style lang="scss" scoped>
-.qlvuhzng {
-	.captcha {
-		margin: 16px 0;
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0e8bdb321e243aa093c3df0109ee47de4a413939
--- /dev/null
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -0,0 +1,272 @@
+<template>
+<div>
+	<div :class="$style.banner">
+		<i class="ti ti-user-edit"></i>
+	</div>
+	<MkSpacer :margin-min="20" :margin-max="32">
+		<form class="_gaps_m" autocomplete="new-password" @submit.prevent="onSubmit">
+			<MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required>
+				<template #label>{{ i18n.ts.invitationCode }}</template>
+				<template #prefix><i class="ti ti-key"></i></template>
+			</MkInput>
+			<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:model-value="onChangeUsername">
+				<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template>
+				<template #prefix>@</template>
+				<template #suffix>@{{ host }}</template>
+				<template #caption>
+					<div><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.cannotBeChangedLater }}</div>
+					<span v-if="usernameState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span>
+					<span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
+					<span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
+					<span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
+					<span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span>
+					<span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span>
+					<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span>
+				</template>
+			</MkInput>
+			<MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:model-value="onChangeEmail">
+				<template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template>
+				<template #prefix><i class="ti ti-mail"></i></template>
+				<template #caption>
+					<span v-if="emailState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span>
+					<span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span>
+					<span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span>
+					<span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span>
+					<span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span>
+					<span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span>
+					<span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span>
+					<span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span>
+					<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
+				</template>
+			</MkInput>
+			<MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:model-value="onChangePassword">
+				<template #label>{{ i18n.ts.password }}</template>
+				<template #prefix><i class="ti ti-lock"></i></template>
+				<template #caption>
+					<span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span>
+					<span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span>
+					<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span>
+				</template>
+			</MkInput>
+			<MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:model-value="onChangePasswordRetype">
+				<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
+				<template #prefix><i class="ti ti-lock"></i></template>
+				<template #caption>
+					<span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span>
+					<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span>
+				</template>
+			</MkInput>
+			<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
+			<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
+			<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
+			<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
+				<template v-if="submitting">
+					<MkLoading :em="true" :colored="false"/>
+				</template>
+				<template v-else>{{ i18n.ts.start }}</template>
+			</MkButton>
+		</form>
+	</MkSpacer>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import getPasswordStrength from 'syuilo-password-strength';
+import { toUnicode } from 'punycode/';
+import MkButton from './MkButton.vue';
+import MkInput from './MkInput.vue';
+import MkSwitch from './MkSwitch.vue';
+import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
+import * as config from '@/config';
+import * as os from '@/os';
+import { login } from '@/account';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
+
+const props = withDefaults(defineProps<{
+	autoSet?: boolean;
+}>(), {
+	autoSet: false,
+});
+
+const emit = defineEmits<{
+	(ev: 'signup', user: Record<string, any>): void;
+	(ev: 'signupEmailPending'): void;
+}>();
+
+const host = toUnicode(config.host);
+
+let hcaptcha = $ref<Captcha | undefined>();
+let recaptcha = $ref<Captcha | undefined>();
+let turnstile = $ref<Captcha | undefined>();
+
+let username: string = $ref('');
+let password: string = $ref('');
+let retypedPassword: string = $ref('');
+let invitationCode: string = $ref('');
+let email = $ref('');
+let usernameState: null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range' = $ref(null);
+let emailState: null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error' = $ref(null);
+let passwordStrength: '' | 'low' | 'medium' | 'high' = $ref('');
+let passwordRetypeState: null | 'match' | 'not-match' = $ref(null);
+let submitting: boolean = $ref(false);
+let hCaptchaResponse = $ref(null);
+let reCaptchaResponse = $ref(null);
+let turnstileResponse = $ref(null);
+let usernameAbortController: null | AbortController = $ref(null);
+let emailAbortController: null | AbortController = $ref(null);
+
+const shouldDisableSubmitting = $computed((): boolean => {
+	return submitting ||
+		instance.enableHcaptcha && !hCaptchaResponse ||
+		instance.enableRecaptcha && !reCaptchaResponse ||
+		instance.enableTurnstile && !turnstileResponse ||
+		instance.emailRequiredForSignup && emailState !== 'ok' ||
+		usernameState !== 'ok' ||
+		passwordRetypeState !== 'match';
+});
+
+function onChangeUsername(): void {
+	if (username === '') {
+		usernameState = null;
+		return;
+	}
+
+	{
+		const err =
+			!username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
+			username.length < 1 ? 'min-range' :
+			username.length > 20 ? 'max-range' :
+			null;
+
+		if (err) {
+			usernameState = err;
+			return;
+		}
+	}
+
+	if (usernameAbortController != null) {
+		usernameAbortController.abort();
+	}
+	usernameState = 'wait';
+	usernameAbortController = new AbortController();
+
+	os.api('username/available', {
+		username,
+	}, undefined, usernameAbortController.signal).then(result => {
+		usernameState = result.available ? 'ok' : 'unavailable';
+	}).catch((err) => {
+		if (err.name !== 'AbortError') {
+			usernameState = 'error';
+		}
+	});
+}
+
+function onChangeEmail(): void {
+	if (email === '') {
+		emailState = null;
+		return;
+	}
+
+	if (emailAbortController != null) {
+		emailAbortController.abort();
+	}
+	emailState = 'wait';
+	emailAbortController = new AbortController();
+
+	os.api('email-address/available', {
+		emailAddress: email,
+	}, undefined, emailAbortController.signal).then(result => {
+		emailState = result.available ? 'ok' :
+			result.reason === 'used' ? 'unavailable:used' :
+			result.reason === 'format' ? 'unavailable:format' :
+			result.reason === 'disposable' ? 'unavailable:disposable' :
+			result.reason === 'mx' ? 'unavailable:mx' :
+			result.reason === 'smtp' ? 'unavailable:smtp' :
+			'unavailable';
+	}).catch((err) => {
+		if (err.name !== 'AbortError') {
+			emailState = 'error';
+		}
+	});
+}
+
+function onChangePassword(): void {
+	if (password === '') {
+		passwordStrength = '';
+		return;
+	}
+
+	const strength = getPasswordStrength(password);
+	passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
+}
+
+function onChangePasswordRetype(): void {
+	if (retypedPassword === '') {
+		passwordRetypeState = null;
+		return;
+	}
+
+	passwordRetypeState = password === retypedPassword ? 'match' : 'not-match';
+}
+
+async function onSubmit(): Promise<void> {
+	if (submitting) return;
+	submitting = true;
+
+	try {
+		await os.api('signup', {
+			username,
+			password,
+			emailAddress: email,
+			invitationCode,
+			'hcaptcha-response': hCaptchaResponse,
+			'g-recaptcha-response': reCaptchaResponse,
+			'turnstile-response': turnstileResponse,
+		});
+		if (instance.emailRequiredForSignup) {
+			os.alert({
+				type: 'success',
+				title: i18n.ts._signup.almostThere,
+				text: i18n.t('_signup.emailSent', { email }),
+			});
+			emit('signupEmailPending');
+		} else {
+			const res = await os.api('signin', {
+				username,
+				password,
+			});
+			emit('signup', res);
+
+			if (props.autoSet) {
+				return login(res.i);
+			}
+		}
+	} catch {
+		submitting = false;
+		hcaptcha?.reset?.();
+		recaptcha?.reset?.();
+		turnstile?.reset?.();
+
+		os.alert({
+			type: 'error',
+			text: i18n.ts.somethingHappened,
+		});
+	}
+}
+</script>
+
+<style lang="scss" module>
+.banner {
+	padding: 16px;
+	text-align: center;
+	font-size: 26px;
+	background-color: var(--accentedBg);
+	color: var(--accent);
+}
+
+.captcha {
+	margin: 16px 0;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2d9545573002d5b0fb06eb4cf8eda35b8f124c8a
--- /dev/null
+++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
@@ -0,0 +1,94 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { expect } from '@storybook/jest';
+import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { StoryObj } from '@storybook/vue3';
+import { onBeforeUnmount } from 'vue';
+import MkSignupServerRules from './MkSignupDialog.rules.vue';
+import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+export const Empty = {
+	render(args) {
+		return {
+			components: {
+				MkSignupServerRules,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkSignupServerRules v-bind="props" />',
+		};
+	},
+	async play({ canvasElement }) {
+		const canvas = within(canvasElement);
+		const groups = await canvas.findAllByRole('group');
+		const buttons = await canvas.findAllByRole('button');
+		for (const group of groups) {
+			if (group.ariaExpanded === 'true') {
+				continue;
+			}
+			const button = await within(group).findByRole('button');
+			userEvent.click(button);
+			await waitFor(() => expect(group).toHaveAttribute('aria-expanded', 'true'));
+		}
+		const labels = await canvas.findAllByText(i18n.ts.agree);
+		for (const label of labels) {
+			expect(buttons.at(-1)).toBeDisabled();
+			await waitFor(() => userEvent.click(label));
+		}
+		expect(buttons.at(-1)).toBeEnabled();
+	},
+	args: {
+		serverRules: [],
+		tosUrl: null,
+	},
+	decorators: [
+		(_, context) => ({
+			setup() {
+				instance.serverRules = context.args.serverRules;
+				instance.tosUrl = context.args.tosUrl;
+				onBeforeUnmount(() => {
+					// FIXME: 呼び出されない
+					instance.serverRules = [];
+					instance.tosUrl = null;
+				});
+			},
+			template: '<story/>',
+		}),
+	],
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkSignupServerRules>;
+export const ServerRulesOnly = {
+	...Empty,
+	args: {
+		...Empty.args,
+		serverRules: [
+			'ルール',
+		],
+	},
+} satisfies StoryObj<typeof MkSignupServerRules>;
+export const TOSOnly = {
+	...Empty,
+	args: {
+		...Empty.args,
+		tosUrl: 'https://example.com/tos',
+	},
+} satisfies StoryObj<typeof MkSignupServerRules>;
+export const ServerRulesAndTOS = {
+	...Empty,
+	args: {
+		...Empty.args,
+		serverRules: ServerRulesOnly.args.serverRules,
+		tosUrl: TOSOnly.args.tosUrl,
+	},
+} satisfies StoryObj<typeof MkSignupServerRules>;
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
new file mode 100644
index 0000000000000000000000000000000000000000..6da81c3bcb081f65b6af6fb0bf4a1776c443efa7
--- /dev/null
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -0,0 +1,124 @@
+<template>
+<div>
+	<div :class="$style.banner">
+		<i class="ti ti-checklist"></i>
+	</div>
+	<MkSpacer :margin-min="20" :margin-max="28">
+		<div class="_gaps_m">
+			<div v-if="instance.disableRegistration">
+				<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+			</div>
+
+			<div style="text-align: center;">{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}</div>
+
+			<MkFolder v-if="availableServerRules" :default-open="true">
+				<template #label>{{ i18n.ts.serverRules }}</template>
+				<template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--success)"></i></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>
+
+				<MkSwitch v-model="agreeServerRules" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch>
+			</MkFolder>
+
+			<MkFolder v-if="availableTos" :default-open="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 v-model="agreeTos" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch>
+			</MkFolder>
+
+			<MkFolder :default-open="true">
+				<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
+				<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template>
+
+				<a href="https://misskey-hub.net/docs/notes.html" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a>
+
+				<MkSwitch v-model="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree>{{ i18n.ts.agree }}</MkSwitch>
+			</MkFolder>
+
+			<div v-if="!agreed" style="text-align: center;">{{ i18n.ts.pleaseAgreeAllToContinue }}</div>
+
+			<div class="_buttonsCenter">
+				<MkButton inline rounded @click="emit('cancel')">{{ i18n.ts.cancel }}</MkButton>
+				<MkButton inline primary rounded gradate :disabled="!agreed" data-cy-signup-rules-continue @click="emit('done')">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+			</div>
+		</div>
+	</MkSpacer>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkInfo from '@/components/MkInfo.vue';
+
+const availableServerRules = instance.serverRules.length > 0;
+const availableTos = instance.tosUrl != null;
+
+const agreeServerRules = ref(false);
+const agreeTos = ref(false);
+const agreeNote = ref(false);
+
+const agreed = computed(() => {
+	return (!availableServerRules || agreeServerRules.value) && (!availableTos || agreeTos.value) && agreeNote.value;
+});
+
+const emit = defineEmits<{
+	(ev: 'cancel'): void;
+	(ev: 'done'): void;
+}>();
+</script>
+
+<style lang="scss" module>
+.banner {
+	padding: 16px;
+	text-align: center;
+	font-size: 26px;
+	background-color: var(--accentedBg);
+	color: var(--accent);
+}
+
+.rules {
+	counter-reset: item;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.rule {
+	display: flex;
+	gap: 8px;
+	word-break: break-word;
+
+	&::before {
+		flex-shrink: 0;
+		display: flex;
+		position: sticky;
+		top: calc(var(--stickyTop, 0px) + 8px);
+		counter-increment: item;
+		content: counter(item);
+		width: 32px;
+		height: 32px;
+		line-height: 32px;
+		background-color: var(--accentedBg);
+		color: var(--accent);
+		font-size: 13px;
+		font-weight: bold;
+		align-items: center;
+		justify-content: center;
+		border-radius: 999px;
+	}
+}
+
+.ruleText {
+	padding-top: 6px;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 790c1e94df43d7ad5f9d0321952e935b3aa6a2b4..17f8b864254a7e7782ee32953153fabf90376bba 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -1,24 +1,40 @@
 <template>
 <MkModalWindow
 	ref="dialog"
-	:width="366"
-	:height="500"
+	:width="500"
+	:height="600"
 	@close="dialog.close()"
 	@closed="$emit('closed')"
 >
 	<template #header>{{ i18n.ts.signup }}</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
-		<XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/>
-	</MkSpacer>
+	<div style="overflow-x: clip;">
+		<Transition
+			mode="out-in"
+			:enter-active-class="$style.transition_x_enterActive"
+			:leave-active-class="$style.transition_x_leaveActive"
+			:enter-from-class="$style.transition_x_enterFrom"
+			:leave-to-class="$style.transition_x_leaveTo"
+		>
+			<template v-if="!isAcceptedServerRule">
+				<XServerRules @done="isAcceptedServerRule = true" @cancel="dialog.close()"/>
+			</template>
+			<template v-else>
+				<XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/>
+			</template>
+		</Transition>
+	</div>
 </MkModalWindow>
 </template>
 
 <script lang="ts" setup>
 import { } from 'vue';
-import XSignup from '@/components/MkSignup.vue';
+import { $ref } from 'vue/macros';
+import XSignup from '@/components/MkSignupDialog.form.vue';
+import XServerRules from '@/components/MkSignupDialog.rules.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n';
+import { instance } from '@/instance';
 
 const props = withDefaults(defineProps<{
 	autoSet?: boolean;
@@ -33,6 +49,8 @@ const emit = defineEmits<{
 
 const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
 
+const isAcceptedServerRule = $ref(false);
+
 function onSignup(res) {
 	emit('done', res);
 	dialog.close();
@@ -42,3 +60,18 @@ function onSignupEmailPending() {
 	dialog.close();
 }
 </script>
+
+<style lang="scss" module>
+.transition_x_enterActive,
+.transition_x_leaveActive {
+	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
+}
+.transition_x_enterFrom {
+	opacity: 0;
+	transform: translateX(50px);
+}
+.transition_x_leaveTo {
+	opacity: 0;
+	transform: translateX(-50px);
+}
+</style>
diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue
index 8bb8637dda7e4c3e1c734cfab6f17e0d7c0d01a3..d9f6716f92d6039a2f2c8ce2dee06d3f916f8701 100644
--- a/packages/frontend/src/components/MkSwitch.vue
+++ b/packages/frontend/src/components/MkSwitch.vue
@@ -9,7 +9,7 @@
 		:disabled="disabled"
 		@keydown.enter="toggle"
 	>
-	<span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" @click.prevent="toggle">
+	<span ref="button" v-tooltip="checked ? i18n.ts.itsOn : i18n.ts.itsOff" class="button" data-cy-switch-toggle @click.prevent="toggle">
 		<div class="knob"></div>
 	</span>
 	<span class="label">
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index 5086c1b319502bd72da15e6a1c00c12e94d968a4..6349ada65a376e4c3f64ffc4bf3808f9c49a39b8 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -1,30 +1,30 @@
 <template>
-<div class="_panel vjnjpkug">
-	<div class="banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
-	<MkAvatar class="avatar" :user="user" indicator/>
-	<div class="title">
-		<MkA class="name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
-		<p class="username"><MkAcct :user="user"/></p>
+<div class="_panel" :class="$style.root">
+	<div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
+	<MkAvatar :class="$style.avatar" :user="user" indicator/>
+	<div :class="$style.title">
+		<MkA :class="$style.name" :to="userPage(user)"><MkUserName :user="user" :nowrap="false"/></MkA>
+		<p :class="$style.username"><MkAcct :user="user"/></p>
 	</div>
-	<span v-if="$i && $i.id !== user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
-	<div class="description">
+	<span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
+	<div :class="$style.description">
 		<div v-if="user.description" class="mfm">
 			<Mfm :text="user.description" :author="user" :i="$i"/>
 		</div>
 		<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
 	</div>
-	<div class="status">
-		<div>
-			<p>{{ i18n.ts.notes }}</p><span>{{ user.notesCount }}</span>
+	<div :class="$style.status">
+		<div :class="$style.statusItem">
+			<p :class="$style.statusItemLabel">{{ i18n.ts.notes }}</p><span :class="$style.statusItemValue">{{ user.notesCount }}</span>
 		</div>
-		<div>
-			<p>{{ i18n.ts.following }}</p><span>{{ user.followingCount }}</span>
+		<div :class="$style.statusItem">
+			<p :class="$style.statusItemLabel">{{ i18n.ts.following }}</p><span :class="$style.statusItemValue">{{ user.followingCount }}</span>
 		</div>
-		<div>
-			<p>{{ i18n.ts.followers }}</p><span>{{ user.followersCount }}</span>
+		<div :class="$style.statusItem">
+			<p :class="$style.statusItemLabel">{{ i18n.ts.followers }}</p><span :class="$style.statusItemValue">{{ user.followersCount }}</span>
 		</div>
 	</div>
-	<MkFollowButton v-if="$i && user.id != $i.id" class="koudoku-button" :user="user" mini/>
+	<MkFollowButton v-if="$i && user.id != $i.id" :class="$style.follow" :user="user" mini/>
 </div>
 </template>
 
@@ -40,99 +40,99 @@ defineProps<{
 }>();
 </script>
 
-<style lang="scss" scoped>
-.vjnjpkug {
+<style lang="scss" module>
+.root {
 	position: relative;
+}
+
+.banner {
+	height: 84px;
+	background-color: rgba(0, 0, 0, 0.1);
+	background-size: cover;
+	background-position: center;
+}
+
+.avatar {
+	display: block;
+	position: absolute;
+	top: 62px;
+	left: 13px;
+	z-index: 2;
+	width: 58px;
+	height: 58px;
+	border: solid 4px var(--panel);
+}
+
+.title {
+	display: block;
+	padding: 10px 0 10px 88px;
+}
+
+.name {
+	display: inline-block;
+	margin: 0;
+	font-weight: bold;
+	line-height: 16px;
+	word-break: break-all;
+}
+
+.username {
+	display: block;
+	margin: 0;
+	line-height: 16px;
+	font-size: 0.8em;
+	color: var(--fg);
+	opacity: 0.7;
+}
+
+.followed {
+	position: absolute;
+	top: 12px;
+	left: 12px;
+	padding: 4px 8px;
+	color: #fff;
+	background: rgba(0, 0, 0, 0.7);
+	font-size: 0.7em;
+	border-radius: 6px;
+}
+
+.description {
+	padding: 16px;
+	font-size: 0.8em;
+	border-top: solid 0.5px var(--divider);
+}
+
+.mfm {
+	display: -webkit-box;
+	-webkit-line-clamp: 3;
+	-webkit-box-orient: vertical;  
+	overflow: hidden;
+}
+
+.status {
+	padding: 10px 16px;
+	border-top: solid 0.5px var(--divider);
+}
+
+.statusItem {
+	display: inline-block;
+	width: 33%;
+}
+
+.statusItemLabel {
+	margin: 0;
+	font-size: 0.7em;
+	color: var(--fg);
+}
+
+.statusItemValue {
+	font-size: 1em;
+	color: var(--accent);
+}
 
-	> .banner {
-		height: 84px;
-		background-color: rgba(0, 0, 0, 0.1);
-		background-size: cover;
-		background-position: center;
-	}
-
-	> .avatar {
-		display: block;
-		position: absolute;
-		top: 62px;
-		left: 13px;
-		z-index: 2;
-		width: 58px;
-		height: 58px;
-		border: solid 4px var(--panel);
-	}
-
-	> .title {
-		display: block;
-		padding: 10px 0 10px 88px;
-
-		> .name {
-			display: inline-block;
-			margin: 0;
-			font-weight: bold;
-			line-height: 16px;
-			word-break: break-all;
-		}
-
-		> .username {
-			display: block;
-			margin: 0;
-			line-height: 16px;
-			font-size: 0.8em;
-			color: var(--fg);
-			opacity: 0.7;
-		}
-	}
-	
-	> .followed {
-		position: absolute;
-		top: 12px;
-		left: 12px;
-		padding: 4px 8px;
-		color: #fff;
-		background: rgba(0, 0, 0, 0.7);
-		font-size: 0.7em;
-		border-radius: 6px;
-	}
-	
-	> .description {
-		padding: 16px;
-		font-size: 0.8em;
-		border-top: solid 0.5px var(--divider);
-
-		> .mfm {
-			display: -webkit-box;
-			-webkit-line-clamp: 3;
-			-webkit-box-orient: vertical;  
-			overflow: hidden;
-		}
-	}
-
-	> .status {
-		padding: 10px 16px;
-		border-top: solid 0.5px var(--divider);
-
-		> div {
-			display: inline-block;
-			width: 33%;
-
-			> p {
-				margin: 0;
-				font-size: 0.7em;
-				color: var(--fg);
-			}
-
-			> span {
-				font-size: 1em;
-				color: var(--accent);
-			}
-		}
-	}
-
-	> .koudoku-button {
-		position: absolute;
-		top: 8px;
-		right: 8px;
-	}
+.follow {
+	position: absolute;
+	top: 8px;
+	right: 8px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index 51eb426e97d2d704a18044252c1a126b7e081a8e..3571ca84d9c27f5f41bf57961dcf762d7273cd1f 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -8,7 +8,7 @@
 	</template>
 
 	<template #default="{ items }">
-		<div class="efvhhmdq">
+		<div :class="$style.root">
 			<MkUserInfo v-for="item in items" :key="item.id" class="user" :user="extractor(item)"/>
 		</div>
 	</template>
@@ -29,8 +29,8 @@ const props = withDefaults(defineProps<{
 });
 </script>
 
-<style lang="scss" scoped>
-.efvhhmdq {
+<style lang="scss" module>
+.root {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
 	grid-gap: var(--margin);
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7d5a65f41a2f3a652ed92701a692527486819e83
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
@@ -0,0 +1,51 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { commonHandlers } from '../../.storybook/mocks';
+import { userDetailed } from '../../.storybook/fakes';
+import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkUserSetupDialog_Follow,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkUserSetupDialog_Follow v-bind="props" />',
+		};
+	},
+	args: {
+		
+	},
+	parameters: {
+		layout: 'centered',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users', (req, res, ctx) => {
+					return res(ctx.json([
+						userDetailed('44'),
+						userDetailed('49'),
+					]));
+				}),
+				rest.post('/api/pinned-users', (req, res, ctx) => {
+					return res(ctx.json([
+						userDetailed('44'),
+						userDetailed('49'),
+					]));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkUserSetupDialog_Follow>;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
new file mode 100644
index 0000000000000000000000000000000000000000..b89e3e4c9d076fed5078ae2a62cb6e3f9737af66
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -0,0 +1,63 @@
+<template>
+<div class="_gaps">
+	<div style="text-align: center;">{{ i18n.ts._initialAccountSetting.followUsers }}</div>
+
+	<MkFolder :default-open="true">
+		<template #label>{{ i18n.ts.recommended }}</template>
+
+		<MkPagination :pagination="pinnedUsers">
+			<template #default="{ items }">
+				<div :class="$style.users">
+					<XUser v-for="item in items" :key="item.id" :user="item"/>
+				</div>
+			</template>
+		</MkPagination>
+	</MkFolder>
+
+	<MkFolder :default-open="true">
+		<template #label>{{ i18n.ts.popularUsers }}</template>
+
+		<MkPagination :pagination="popularUsers">
+			<template #default="{ items }">
+				<div :class="$style.users">
+					<XUser v-for="item in items" :key="item.id" :user="item"/>
+				</div>
+			</template>
+		</MkPagination>
+	</MkFolder>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import XUser from '@/components/MkUserSetupDialog.User.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import * as os from '@/os';
+import { $i } from '@/account';
+import MkPagination from '@/components/MkPagination.vue';
+
+const emit = defineEmits<{
+	(ev: 'done'): void;
+}>();
+
+const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
+
+const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
+	state: 'alive',
+	origin: 'local',
+	sort: '+follower',
+} };
+</script>
+
+<style lang="scss" module>
+.users {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
+	grid-gap: var(--margin);
+	justify-content: center;
+}
+</style>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f4930aa26b0ba2adf535c31986df9b223daef9fc
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
@@ -0,0 +1,31 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import MkUserSetupDialog_Profile from './MkUserSetupDialog.Profile.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkUserSetupDialog_Profile,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkUserSetupDialog_Profile v-bind="props" />',
+		};
+	},
+	args: {
+		
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkUserSetupDialog_Profile>;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
new file mode 100644
index 0000000000000000000000000000000000000000..adb8d4334965f1be82dbcc245ef797980a596130
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -0,0 +1,101 @@
+<template>
+<div class="_gaps">
+	<MkInfo>{{ i18n.ts._initialAccountSetting.theseSettingsCanEditLater }}</MkInfo>
+
+	<FormSlot>
+		<template #label>{{ i18n.ts.avatar }}</template>
+		<div v-adaptive-bg :class="$style.avatarSection" class="_panel">
+			<MkAvatar :class="$style.avatar" :user="$i" @click="setAvatar"/>
+			<div style="margin-top: 16px;">
+				<MkButton primary rounded inline @click="setAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
+			</div>
+		</div>
+	</FormSlot>
+
+	<MkInput v-model="name" :max="30" manual-save data-cy-user-setup-user-name>
+		<template #label>{{ i18n.ts._profile.name }}</template>
+	</MkInput>
+
+	<MkTextarea v-model="description" :max="500" tall manual-save data-cy-user-setup-user-description>
+		<template #label>{{ i18n.ts._profile.description }}</template>
+	</MkTextarea>
+
+	<MkInfo>{{ i18n.ts._initialAccountSetting.youCanEditMoreSettingsInSettingsPageLater }}</MkInfo>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
+import FormSlot from '@/components/form/slot.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { chooseFileFromPc } from '@/scripts/select-file';
+import * as os from '@/os';
+import { $i } from '@/account';
+
+const emit = defineEmits<{
+	(ev: 'done'): void;
+}>();
+
+const name = ref('');
+const description = ref('');
+
+watch(name, () => {
+	os.apiWithDialog('i/update', {
+		// 空文字列をnullにしたいので??は使うな
+		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+		name: name.value || null,
+	});
+});
+
+watch(description, () => {
+	os.apiWithDialog('i/update', {
+		// 空文字列をnullにしたいので??は使うな
+		// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
+		description: description.value || null,
+	});
+});
+
+function setAvatar(ev) {
+	chooseFileFromPc(false).then(async (files) => {
+		const file = files[0];
+
+		let originalOrCropped = file;
+
+		const { canceled } = await os.confirm({
+			type: 'question',
+			text: i18n.t('cropImageAsk'),
+			okText: i18n.ts.cropYes,
+			cancelText: i18n.ts.cropNo,
+		});
+
+		if (!canceled) {
+			originalOrCropped = await os.cropImage(file, {
+				aspectRatio: 1,
+			});
+		}
+
+		const i = await os.apiWithDialog('i/update', {
+			avatarId: originalOrCropped.id,
+		});
+		$i.avatarId = i.avatarId;
+		$i.avatarUrl = i.avatarUrl;
+	});
+}
+</script>
+
+<style lang="scss" module>
+.avatarSection {
+	text-align: center;
+	padding: 20px;
+}
+
+.avatar {
+	width: 100px;
+	height: 100px;
+}
+</style>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7413f4884bd5d6c17c3a52d04f1c5db6c6da8ea9
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
@@ -0,0 +1,32 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { userDetailed } from '../../.storybook/fakes';
+import MkUserSetupDialog_User from './MkUserSetupDialog.User.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkUserSetupDialog_User,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkUserSetupDialog_User v-bind="props" />',
+		};
+	},
+	args: {
+		user: userDetailed(),
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkUserSetupDialog_User>;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d66f34f1650b015bb78ddb8757ea4ad785a8f902
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue
@@ -0,0 +1,101 @@
+<template>
+<div v-adaptive-bg class="_panel" style="position: relative;">
+	<div :class="$style.banner" :style="user.bannerUrl ? `background-image: url(${user.bannerUrl})` : ''"></div>
+	<MkAvatar :class="$style.avatar" :user="user" indicator/>
+	<div :class="$style.title">
+		<div :class="$style.name"><MkUserName :user="user" :nowrap="false"/></div>
+		<p :class="$style.username"><MkAcct :user="user"/></p>
+	</div>
+	<div :class="$style.description">
+		<div v-if="user.description" :class="$style.mfm">
+			<Mfm :text="user.description" :author="user" :i="$i"/>
+		</div>
+		<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
+	</div>
+	<div :class="$style.footer">
+		<MkButton v-if="!isFollowing" primary gradate rounded full @click="follow"><i class="ti ti-plus"></i> {{ i18n.ts.follow }}</MkButton>
+		<div v-else style="opacity: 0.7; text-align: center;">{{ i18n.ts.youFollowing }} <i class="ti ti-check"></i></div>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import * as misskey from 'misskey-js';
+import { ref } from 'vue';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n';
+import { $i } from '@/account';
+import * as os from '@/os';
+
+const props = defineProps<{
+	user: misskey.entities.UserDetailed;
+}>();
+
+const isFollowing = ref(false);
+
+async function follow() {
+	isFollowing.value = true;
+	os.api('following/create', {
+		userId: props.user.id,
+	});
+}
+</script>
+
+<style lang="scss" module>
+.banner {
+	height: 60px;
+	background-color: rgba(0, 0, 0, 0.1);
+	background-size: cover;
+	background-position: center;
+}
+
+.avatar {
+	display: block;
+	position: absolute;
+	top: 30px;
+	left: 13px;
+	z-index: 2;
+	width: 58px;
+	height: 58px;
+	border: solid 4px var(--panel);
+}
+
+.title {
+	display: block;
+	padding: 10px 0 10px 88px;
+}
+
+.name {
+	display: inline-block;
+	margin: 0;
+	font-weight: bold;
+	line-height: 16px;
+	word-break: break-all;
+}
+
+.username {
+	display: block;
+	margin: 0;
+	line-height: 16px;
+	font-size: 0.8em;
+	color: var(--fg);
+	opacity: 0.7;
+}
+
+.description {
+	padding: 0 16px 16px 88px;
+	font-size: 0.9em;
+}
+
+.mfm {
+	display: -webkit-box;
+	-webkit-line-clamp: 5;
+	-webkit-box-orient: vertical;  
+	overflow: hidden;
+}
+
+.footer {
+	border-top: solid 0.5px var(--divider);
+	padding: 16px;
+}
+</style>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..55790602d567923b82c792afc73a243c836e3977
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
@@ -0,0 +1,51 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import { rest } from 'msw';
+import { commonHandlers } from '../../.storybook/mocks';
+import { userDetailed } from '../../.storybook/fakes';
+import MkUserSetupDialog from './MkUserSetupDialog.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkUserSetupDialog,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkUserSetupDialog v-bind="props" />',
+		};
+	},
+	args: {
+		
+	},
+	parameters: {
+		layout: 'centered',
+		msw: {
+			handlers: [
+				...commonHandlers,
+				rest.post('/api/users', (req, res, ctx) => {
+					return res(ctx.json([
+						userDetailed('44'),
+						userDetailed('49'),
+					]));
+				}),
+				rest.post('/api/pinned-users', (req, res, ctx) => {
+					return res(ctx.json([
+						userDetailed('44'),
+						userDetailed('49'),
+					]));
+				}),
+			],
+		},
+	},
+} satisfies StoryObj<typeof MkUserSetupDialog>;
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
new file mode 100644
index 0000000000000000000000000000000000000000..096b88c309a58df6487450aef90f777a9e0eeca3
--- /dev/null
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -0,0 +1,145 @@
+<template>
+<MkModalWindow
+	ref="dialog"
+	:width="500"
+	:height="550"
+	data-cy-user-setup
+	@close="close(true)"
+	@closed="emit('closed')"
+>
+	<template #header>{{ i18n.ts.initialAccountSetting }}</template>
+
+	<div style="overflow-x: clip;">
+		<Transition
+			mode="out-in"
+			:enter-active-class="$style.transition_x_enterActive"
+			:leave-active-class="$style.transition_x_leaveActive"
+			:enter-from-class="$style.transition_x_enterFrom"
+			:leave-to-class="$style.transition_x_leaveTo"
+		>
+			<template v-if="page === 0">
+				<div :class="$style.centerPage">
+					<MkSpacer :margin-min="20" :margin-max="28">
+						<div class="_gaps" style="text-align: center;">
+							<i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.accountCreated }}</div>
+							<div>{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}</div>
+							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts._initialAccountSetting.profileSetting }} <i class="ti ti-arrow-right"></i></MkButton>
+						</div>
+					</MkSpacer>
+				</div>
+			</template>
+			<template v-else-if="page === 1">
+				<div style="height: 100cqh; overflow: auto;">
+					<MkSpacer :margin-min="20" :margin-max="28">
+						<XProfile/>
+						<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+					</MkSpacer>
+				</div>
+			</template>
+			<template v-else-if="page === 2">
+				<div style="height: 100cqh; overflow: auto;">
+					<MkSpacer :margin-min="20" :margin-max="28">
+						<XFollow/>
+						<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+					</MkSpacer>
+				</div>
+			</template>
+			<template v-else-if="page === 3">
+				<div :class="$style.centerPage">
+					<MkSpacer :margin-min="20" :margin-max="28">
+						<div class="_gaps" style="text-align: center;">
+							<i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div>
+							<div style="padding: 0 16px;">{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}</div>
+							<MkPushNotificationAllowButton primary show-only-to-register style="margin: 0 auto;"/>
+							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						</div>
+					</MkSpacer>
+				</div>
+			</template>
+			<template v-else-if="page === 4">
+				<div :class="$style.centerPage">
+					<MkSpacer :margin-min="20" :margin-max="28">
+						<div class="_gaps" style="text-align: center;">
+							<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
+							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
+							<I18n :src="i18n.ts._initialAccountSetting.ifYouNeedLearnMore" tag="div" style="padding: 0 16px;">
+								<template #name>{{ instance.name ?? host }}</template>
+								<template #link>
+									<a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a>
+								</template>
+							</I18n>
+							<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
+							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton>
+						</div>
+					</MkSpacer>
+				</div>
+			</template>
+		</Transition>
+	</div>
+</MkModalWindow>
+</template>
+
+<script lang="ts" setup>
+import { ref, shallowRef, watch } from 'vue';
+import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkButton from '@/components/MkButton.vue';
+import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
+import XFollow from '@/components/MkUserSetupDialog.Follow.vue';
+import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+import { host } from '@/config';
+import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
+import { defaultStore } from '@/store';
+import * as os from '@/os';
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+
+const page = ref(defaultStore.state.accountSetupWizard);
+
+watch(page, () => {
+	defaultStore.set('accountSetupWizard', page.value);
+});
+
+async function close(skip: boolean) {
+	if (skip) {
+		const { canceled } = await os.confirm({
+			type: 'warning',
+			text: i18n.ts._initialAccountSetting.skipAreYouSure,
+		});
+		if (canceled) return;
+	}
+
+	dialog.value.close();
+	defaultStore.set('accountSetupWizard', -1);
+}
+</script>
+
+<style lang="scss" module>
+.transition_x_enterActive,
+.transition_x_leaveActive {
+	transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1);
+}
+.transition_x_enterFrom {
+	opacity: 0;
+	transform: translateX(50px);
+}
+.transition_x_leaveTo {
+	opacity: 0;
+	transform: translateX(-50px);
+}
+
+.centerPage {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	height: 100cqh;
+	padding-bottom: 30px;
+	box-sizing: border-box;
+}
+</style>
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
new file mode 100644
index 0000000000000000000000000000000000000000..fb705786cf7305452eac9fe1a3e7a387b906de91
--- /dev/null
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -0,0 +1,157 @@
+<template>
+<div>
+	<MkLoading v-if="fetching"/>
+	<div v-show="!fetching" :class="$style.root">
+		<canvas ref="chartEl"></canvas>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted } from 'vue';
+import { Chart } from 'chart.js';
+import gradient from 'chartjs-plugin-gradient';
+import tinycolor from 'tinycolor2';
+import * as os from '@/os';
+import { defaultStore } from '@/store';
+import { useChartTooltip } from '@/scripts/use-chart-tooltip';
+import { chartVLine } from '@/scripts/chart-vline';
+import { initChart } from '@/scripts/init-chart';
+
+initChart();
+
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
+const now = new Date();
+let chartInstance: Chart = null;
+const chartLimit = 30;
+let fetching = $ref(true);
+
+const { handler: externalTooltipHandler } = useChartTooltip();
+
+async function renderChart() {
+	if (chartInstance) {
+		chartInstance.destroy();
+	}
+
+	const getDate = (ago: number) => {
+		const y = now.getFullYear();
+		const m = now.getMonth();
+		const d = now.getDate();
+
+		return new Date(y, m, d - ago);
+	};
+
+	const format = (arr) => {
+		return arr.map((v, i) => ({
+			x: getDate(i).getTime(),
+			y: v,
+		}));
+	};
+
+	const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
+
+	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+
+	const computedStyle = getComputedStyle(document.documentElement);
+	const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
+
+	const colorRead = accent;
+	const colorWrite = '#2ecc71';
+
+	const max = Math.max(...raw.read);
+
+	chartInstance = new Chart(chartEl, {
+		type: 'bar',
+		data: {
+			datasets: [{
+				parsing: false,
+				label: 'Read',
+				data: format(raw.read).slice().reverse(),
+				pointRadius: 0,
+				borderWidth: 0,
+				borderJoinStyle: 'round',
+				borderRadius: 4,
+				backgroundColor: colorRead,
+				barPercentage: 0.5,
+				categoryPercentage: 1,
+				fill: true,
+			}],
+		},
+		options: {
+			aspectRatio: 2.5,
+			layout: {
+				padding: {
+					left: 0,
+					right: 8,
+					top: 0,
+					bottom: 0,
+				},
+			},
+			scales: {
+				x: {
+					type: 'time',
+					offset: true,
+					time: {
+						stepSize: 1,
+						unit: 'day',
+						displayFormats: {
+							day: 'M/d',
+							month: 'Y/M',
+						},
+					},
+					grid: {
+						display: false,
+					},
+					ticks: {
+						display: true,
+						maxRotation: 0,
+						autoSkipPadding: 8,
+					},
+				},
+				y: {
+					position: 'left',
+					suggestedMax: 10,
+					grid: {
+						display: true,
+					},
+					ticks: {
+						display: true,
+						//mirror: true,
+					},
+				},
+			},
+			interaction: {
+				intersect: false,
+				mode: 'index',
+			},
+			plugins: {
+				legend: {
+					display: false,
+				},
+				tooltip: {
+					enabled: false,
+					mode: 'index',
+					animation: {
+						duration: 0,
+					},
+					external: externalTooltipHandler,
+				},
+				gradient,
+			},
+		},
+		plugins: [chartVLine(vLineColor)],
+	});
+
+	fetching = false;
+}
+
+onMounted(async () => {
+	renderChart();
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	padding: 20px;
+}
+</style>
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..62267681273b140c08ca00bb7a1a2c5afe8449b0
--- /dev/null
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -0,0 +1,227 @@
+<template>
+<div v-if="meta" :class="$style.root">
+	<div :class="[$style.main, $style.panel]">
+		<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.mainIcon"/>
+		<button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
+		<div :class="$style.mainFg">
+			<h1 :class="$style.mainTitle">
+				<!-- 背景色によってはロゴが見えなくなるのでとりあえず無効に -->
+				<!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> -->
+				<span>{{ instanceName }}</span>
+			</h1>
+			<div :class="$style.mainAbout">
+				<!-- eslint-disable-next-line vue/no-v-html -->
+				<div v-html="meta.description || i18n.ts.headlineMisskey"></div>
+			</div>
+			<div v-if="instance.disableRegistration" :class="$style.mainWarn">
+				<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+			</div>
+			<div class="_gaps_s" :class="$style.mainActions">
+				<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
+				<MkButton :class="$style.mainAction" full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton>
+				<MkButton :class="$style.mainAction" full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
+			</div>
+		</div>
+	</div>
+	<div v-if="stats" :class="$style.stats">
+		<div :class="[$style.statsItem, $style.panel]">
+			<div :class="$style.statsItemLabel">{{ i18n.ts.users }}</div>
+			<div :class="$style.statsItemCount"><MkNumber :value="stats.originalUsersCount"/></div>
+		</div>
+		<div :class="[$style.statsItem, $style.panel]">
+			<div :class="$style.statsItemLabel">{{ i18n.ts.notes }}</div>
+			<div :class="$style.statsItemCount"><MkNumber :value="stats.originalNotesCount"/></div>
+		</div>
+	</div>
+	<div v-if="instance.policies.ltlAvailable" :class="[$style.tl, $style.panel]">
+		<div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div>
+		<div :class="$style.tlBody">
+			<MkTimeline src="local"/>
+		</div>
+	</div>
+	<div :class="[$style.activeUsersChart, $style.panel]">
+		<XActiveUsersChart/>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import { Instance } from 'misskey-js/built/entities';
+import XTimeline from './welcome.timeline.vue';
+import XSigninDialog from '@/components/MkSigninDialog.vue';
+import XSignupDialog from '@/components/MkSignupDialog.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkTimeline from '@/components/MkTimeline.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { instanceName } from '@/config';
+import * as os from '@/os';
+import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+import number from '@/filters/number';
+import MkNumber from '@/components/MkNumber.vue';
+import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue';
+
+let meta = $ref<Instance>();
+let stats = $ref(null);
+
+os.api('meta', { detail: true }).then(_meta => {
+	meta = _meta;
+});
+
+os.api('stats', {
+}).then((res) => {
+	stats = res;
+});
+
+function signin() {
+	os.popup(XSigninDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+function signup() {
+	os.popup(XSignupDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+function showMenu(ev) {
+	os.popupMenu([{
+		text: i18n.ts.instanceInfo,
+		icon: 'ti ti-info-circle',
+		action: () => {
+			os.pageWindow('/about');
+		},
+	}, {
+		text: i18n.ts.aboutMisskey,
+		icon: 'ti ti-info-circle',
+		action: () => {
+			os.pageWindow('/about-misskey');
+		},
+	}, null, {
+		text: i18n.ts.help,
+		icon: 'ti ti-help-circle',
+		action: () => {
+			window.open('https://misskey-hub.net/help.md', '_blank');
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
+function exploreOtherServers() {
+	// TODO: 言語をよしなに
+	window.open('https://join.misskey.page/ja-JP/instances', '_blank');
+}
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	display: flex;
+	flex-direction: column;
+	gap: 16px;
+	padding: 32px 0 0 0;
+}
+
+.panel {
+	position: relative;
+	background: var(--panel);
+	border-radius: var(--radius);
+	box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
+}
+
+.main {
+	text-align: center;
+}
+
+.mainIcon {
+	width: 85px;
+	margin-top: -47px;
+	vertical-align: bottom;
+	filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.5));
+}
+
+.mainMenu {
+	position: absolute;
+	top: 16px;
+	right: 16px;
+	width: 32px;
+	height: 32px;
+	border-radius: 8px;
+	font-size: 18px;
+}
+
+.mainFg {
+	position: relative;
+	z-index: 1;
+}
+
+.mainTitle {
+	display: block;
+	margin: 0;
+	padding: 16px 32px 24px 32px;
+	font-size: 1.4em;
+}
+
+.mainLogo {
+	vertical-align: bottom;
+	max-height: 120px;
+	max-width: min(100%, 300px);
+}
+
+.mainAbout {
+	padding: 0 32px;
+}
+
+.mainWarn {
+	padding: 32px 32px 0 32px;
+}
+
+.mainActions {
+	padding: 32px;
+}
+
+.mainAction {
+	line-height: 28px;
+}
+
+.stats {
+	display: grid;
+	grid-template-columns: 1fr 1fr;
+	grid-gap: 16px;
+}
+
+.statsItem {
+	overflow: clip;
+	padding: 16px 20px;
+}
+
+.statsItemLabel {
+	color: var(--fgTransparentWeak);
+	font-size: 0.9em;
+}
+
+.statsItemCount {
+	font-weight: bold;
+	font-size: 1.2em;
+	color: var(--accent);
+}
+
+.tl {
+	overflow: clip;
+}
+
+.tlHeader {
+	padding: 12px 16px;
+	border-bottom: solid 1px var(--divider);
+}
+
+.tlBody {
+	height: 350px;
+	overflow: auto;
+}
+
+.activeUsersChart {
+
+}
+</style>
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index d074fdd1506bd5468558423fed18f17e2ec20cab..33e594acd80be4309248157774f2e8fe0e2f06ba 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -2,11 +2,11 @@
 <div :class="$style.root">
 	<template v-if="edit">
 		<header :class="$style['edit-header']">
-			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" class="mk-widget-select">
+			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
 				<template #label>{{ i18n.ts.selectWidget }}</template>
 				<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
 			</MkSelect>
-			<MkButton inline primary class="mk-widget-add" @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+			<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
 			<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
 		</header>
 		<Sortable
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index 687abed632e501c1d1960c19bc57bceb705da890..b662479b2a28ba032a2656f9890807fa6ce4d6de 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -29,7 +29,7 @@
 					<button v-if="closeButton" v-tooltip="i18n.ts.close" class="_button" :class="$style.headerButton" @click="close()"><i class="ti ti-x"></i></button>
 				</span>
 			</div>
-			<div v-container :class="$style.content">
+			<div :class="$style.content">
 				<slot></slot>
 			</div>
 		</div>
@@ -541,7 +541,7 @@ defineExpose({
 	flex: 1;
 	overflow: auto;
 	background: var(--panel);
-	container-type: inline-size;
+	container-type: size;
 }
 
 $handleSize: 8px;
diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
index d5e3fc3568294c6d4495e79ca67138ac4b790ea1..9d5fd3947dc5222020a05062cf037f5c826d2787 100644
--- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
@@ -41,3 +41,35 @@ export const Detail = {
 		detail: true,
 	},
 } satisfies StoryObj<typeof MkAcct>;
+export const Long = {
+	...Default,
+	args: {
+		...Default.args,
+		user: {
+			...userDetailed(),
+			username: 'the_quick_brown_fox_jumped_over_the_lazy_dog',
+			host: 'misskey.example',
+		},
+	},
+	decorators: [
+		() => ({
+			template: '<div style="width: 360px;"><story/></div>',
+		}),
+	],
+} satisfies StoryObj<typeof MkAcct>;
+export const VeryLong = {
+	...Default,
+	args: {
+		...Default.args,
+		user: {
+			...userDetailed(),
+			username: '2c7cc62a697ea3a7826521f3fd34f0cb273693cbe5e9310f35449f43622a5cdc',
+			host: 'the.quick.brown.fox.jumped.over.the.lazy.dog.very.long.hostname.nostr.example',
+		},
+	},
+	decorators: [
+		() => ({
+			template: '<div style="width: 360px;"><story/></div>',
+		}),
+	],
+} satisfies StoryObj<typeof MkAcct>;
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 2b9f892fc6d06c1797d8719eebc3e7b7b7f90c6c..59358aef70c0aa3dc9a70d9b5bc40c3f1ee77abe 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -1,5 +1,9 @@
 <template>
-<span>
+<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :min-scale="2 / 3">
+	<span>@{{ user.username }}</span>
+	<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
+</MkCondensedLine>
+<span v-else>
 	<span>@{{ user.username }}</span>
 	<span v-if="user.host || detail || defaultStore.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
 </span>
@@ -8,6 +12,7 @@
 <script lang="ts" setup>
 import * as misskey from 'misskey-js';
 import { toUnicode } from 'punycode/';
+import MkCondensedLine from './MkCondensedLine.vue';
 import { host as hostRaw } from '@/config';
 import { defaultStore } from '@/store';
 
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 8497b8443b2ee6c8652b02d3da97d1fafc3d9275..ad36dcabe45e1ff37b7657950e37fa45fea021b8 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -222,7 +222,7 @@ watch(() => props.user.avatarBlurhash, () => {
 			transform: rotate(37.5deg) skew(30deg);
 
 			&, &::after {
-				border-radius: 0 75% 75%;
+				border-radius: 25% 75% 75%;
 			}
 
 			> .layer {
@@ -251,7 +251,7 @@ watch(() => props.user.avatarBlurhash, () => {
 			transform: rotate(-37.5deg) skew(-30deg);
 
 			&, &::after {
-				border-radius: 75% 0 75% 75%;
+				border-radius: 75% 25% 75% 75%;
 			}
 
 			> .layer {
diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ce985bc59f779cb78a55b6e63244a4393fb1ba90
--- /dev/null
+++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
@@ -0,0 +1,39 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import MkCondensedLine from './MkCondensedLine.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkCondensedLine,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkCondensedLine>{{ props.text }}</MkCondensedLine>',
+		};
+	},
+	args: {
+		text: 'This is a condensed line.',
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkCondensedLine>;
+export const ContainerIs100px = {
+	...Default,
+	decorators: [
+		() => ({
+			template: '<div style="width: 100px;"><story/></div>',
+		}),
+	],
+} satisfies StoryObj<typeof MkCondensedLine>;
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1d46ff1ec9f355e0a2110ade6ae2382fe3f5df4b
--- /dev/null
+++ b/packages/frontend/src/components/global/MkCondensedLine.vue
@@ -0,0 +1,65 @@
+<template>
+<span :class="$style.container">
+	<span ref="content" :class="$style.content">
+		<slot/>
+	</span>
+</span>
+</template>
+
+<script lang="ts">
+interface Props {
+	readonly minScale?: number;
+}
+
+const contentSymbol = Symbol();
+const observer = new ResizeObserver((entries) => {
+	for (const entry of entries) {
+		const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement;
+		const props: Required<Props> = content[contentSymbol];
+		const container = content.parentElement as HTMLSpanElement;
+		const contentWidth = content.getBoundingClientRect().width;
+		const containerWidth = container.getBoundingClientRect().width;
+		container.style.transform = `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})`;
+	}
+});
+</script>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+
+const props = withDefaults(defineProps<Props>(), {
+	minScale: 0,
+});
+
+const content = ref<HTMLSpanElement>();
+
+watch(content, (value, oldValue) => {
+	if (oldValue) {
+		delete oldValue[contentSymbol];
+		observer.unobserve(oldValue);
+		if (oldValue.parentElement) {
+			observer.unobserve(oldValue.parentElement);
+		}
+	}
+	if (value) {
+		value[contentSymbol] = props;
+		observer.observe(value);
+		if (value.parentElement) {
+			observer.observe(value.parentElement);
+		}
+	}
+});
+</script>
+
+<style module lang="scss">
+.container {
+	display: inline-block;
+	max-width: 100%;
+	transform-origin: 0;
+}
+
+.content {
+	display: inline-block;
+	white-space: nowrap;
+}
+</style>
diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts
index 60ac5c91ad72b87909d49dd3b73d512b5fff7266..8252a4d76eb9cc28c3b68fe23ce1114ac98c6eef 100644
--- a/packages/frontend/src/components/global/MkError.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkError.stories.impl.ts
@@ -1,4 +1,5 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { action } from '@storybook/addon-actions';
 import { expect } from '@storybook/jest';
 import { waitFor } from '@storybook/testing-library';
 import { StoryObj } from '@storybook/vue3';
@@ -20,14 +21,21 @@ export const Default = {
 						...this.args,
 					};
 				},
+				events() {
+					return {
+						retry: action('retry'),
+					};
+				},
 			},
-			template: '<MkError v-bind="props" />',
+			template: '<MkError v-bind="props" v-on="events" />',
 		};
 	},
 	async play({ canvasElement }) {
 		await expect(canvasElement.firstElementChild).not.toBeNull();
 		await waitFor(async () => expect(canvasElement.firstElementChild?.classList).not.toContain('_transition_zoom-enter-active'));
 	},
+	args: {
+	},
 	parameters: {
 		layout: 'centered',
 	},
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 710edd797ae5e8615338e4af78df85ccc83ee393..b91d378b17ee76d64c1b792d47fbc365bf018b05 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -156,7 +156,7 @@ onUnmounted(() => {
 	}
 
 	&.thin {
-		--height: 42px;
+		--height: 40px;
 
 		> .buttons {
 			> .button {
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 99169512dbd74316fc93735f5ab669f247377b25..261cc0ee18ca2f93c7ac927d1aa2cb5f4af8d0ea 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -8,6 +8,7 @@
 </template>
 
 <script lang="ts" setup>
+import isChromatic from 'chromatic/isChromatic';
 import { onUnmounted } from 'vue';
 import { i18n } from '@/i18n';
 import { dateTimeFormat } from '@/scripts/intl-const';
@@ -17,7 +18,7 @@ const props = withDefaults(defineProps<{
 	origin?: Date | null;
 	mode?: 'relative' | 'absolute' | 'detail';
 }>(), {
-	origin: null,
+	origin: isChromatic() ? new Date('2023-04-01T00:00:00Z') : null,
 	mode: 'relative',
 });
 
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 63e8fc225c9834b1c6685759feeb138cab725871..4ef8111da910dea910c82d6ce5e9ebf31d143848 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -5,6 +5,7 @@ import MkA from './global/MkA.vue';
 import MkAcct from './global/MkAcct.vue';
 import MkAvatar from './global/MkAvatar.vue';
 import MkEmoji from './global/MkEmoji.vue';
+import MkCondensedLine from './global/MkCondensedLine.vue';
 import MkCustomEmoji from './global/MkCustomEmoji.vue';
 import MkUserName from './global/MkUserName.vue';
 import MkEllipsis from './global/MkEllipsis.vue';
@@ -33,6 +34,7 @@ export const components = {
 	MkAcct: MkAcct,
 	MkAvatar: MkAvatar,
 	MkEmoji: MkEmoji,
+	MkCondensedLine: MkCondensedLine,
 	MkCustomEmoji: MkCustomEmoji,
 	MkUserName: MkUserName,
 	MkEllipsis: MkEllipsis,
@@ -55,6 +57,7 @@ declare module '@vue/runtime-core' {
 		MkAcct: typeof MkAcct;
 		MkAvatar: typeof MkAvatar;
 		MkEmoji: typeof MkEmoji;
+		MkCondensedLine: typeof MkCondensedLine;
 		MkCustomEmoji: typeof MkCustomEmoji;
 		MkUserName: typeof MkUserName;
 		MkEllipsis: typeof MkEllipsis;
diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts
index 073b21a0ae2249fa3a93f3379ddb59176f6552df..24f60910d1cfc6646ba9488e371c6628be97faf4 100644
--- a/packages/frontend/src/config.ts
+++ b/packages/frontend/src/config.ts
@@ -1,21 +1,22 @@
-import { miLocalStorage } from "./local-storage";
+import { miLocalStorage } from './local-storage';
 
 const address = new URL(location.href);
-const siteName = (document.querySelector('meta[property="og:site_name"]') as HTMLMetaElement)?.content;
+const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site_name"]')?.content;
 
 export const host = address.host;
 export const hostname = address.hostname;
 export const url = address.origin;
 export const apiUrl = url + '/api';
 export const wsUrl = url.replace('http://', 'ws://').replace('https://', 'wss://') + '/streaming';
-export const lang = miLocalStorage.getItem('lang');
+export const lang = miLocalStorage.getItem('lang') ?? 'en-US';
 export const langs = _LANGS_;
-export let locale = JSON.parse(miLocalStorage.getItem('locale'));
+const preParseLocale = miLocalStorage.getItem('locale');
+export let locale = preParseLocale ? JSON.parse(preParseLocale) : null;
 export const version = _VERSION_;
 export const instanceName = siteName === 'Misskey' ? host : siteName;
 export const ui = miLocalStorage.getItem('ui');
 export const debug = miLocalStorage.getItem('debug') === 'true';
 
-export function updateLocale(newLocale) {
+export function updateLocale(newLocale): void {
 	locale = newLocale;
 }
diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts
index 1d1b8fcea4f4fe6f25ace20b686641d79cb4d4c8..aaa3d10302546867a6e64d66f97156e910f99cbd 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend/src/const.ts
@@ -35,6 +35,11 @@ export const FILE_TYPE_BROWSERSAFE = [
 	'audio/webm',
 
 	'audio/aac',
+
+	// see https://github.com/misskey-dev/misskey/pull/10686
+	'audio/flac',
+	'audio/wav',
+	// backward compatibility
 	'audio/x-flac',
 	'audio/vnd.wave',
 ];
@@ -56,6 +61,7 @@ export const ROLE_POLICIES = [
 	'canSearchNotes',
 	'canHideAds',
 	'driveCapacityMb',
+	'alwaysMarkNsfw',
 	'pinLimit',
 	'antennaLimit',
 	'wordMuteLimit',
diff --git a/packages/frontend/src/directives/container.ts b/packages/frontend/src/directives/container.ts
deleted file mode 100644
index a8a93eb9be4c00d7eb3d31488a12fd1141e044d4..0000000000000000000000000000000000000000
--- a/packages/frontend/src/directives/container.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { Directive } from 'vue';
-
-const map = new WeakMap<HTMLElement, ResizeObserver>();
-
-export default {
-	mounted(el: HTMLElement, binding, vn) {
-		const ro = new ResizeObserver((entries, observer) => {
-			el.style.setProperty('--containerHeight', el.offsetHeight + 'px');
-		});
-		ro.observe(el);
-		map.set(el, ro);
-	},
-
-	unmounted(el, binding, vn) {
-		const ro = map.get(el);
-		if (ro) {
-			ro.disconnect();
-			map.delete(el);
-		}
-	},
-} as Directive;
diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts
index 064ee4f64b40ef8ca17b5f380e6ef7d2ca512665..7847d661d41f3a1c683c3e45d882a5b72d58167a 100644
--- a/packages/frontend/src/directives/index.ts
+++ b/packages/frontend/src/directives/index.ts
@@ -11,7 +11,6 @@ import clickAnime from './click-anime';
 import panel from './panel';
 import adaptiveBorder from './adaptive-border';
 import adaptiveBg from './adaptive-bg';
-import container from './container';
 
 export default function(app: App) {
 	for (const [key, value] of Object.entries(directives)) {
@@ -32,5 +31,4 @@ export const directives = {
 	'panel': panel,
 	'adaptive-border': adaptiveBorder,
 	'adaptive-bg': adaptiveBg,
-	'container': container,
 };
diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts
index 5b3e7ec9325c4fb72b72f34f4f80abd66a18ce96..49e7bb4008545a4032f60b342f8096fe55151427 100644
--- a/packages/frontend/src/init.ts
+++ b/packages/frontend/src/init.ts
@@ -6,18 +6,6 @@ import 'vite/modulepreload-polyfill';
 
 import '@/style.scss';
 
-//#region account indexedDB migration
-import { set } from '@/scripts/idb-proxy';
-
-{
-	const accounts = miLocalStorage.getItem('accounts');
-	if (accounts) {
-		set('accounts', JSON.parse(accounts));
-		miLocalStorage.removeItem('accounts');
-	}
-}
-//#endregion
-
 import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
 import { compareVersions } from 'compare-versions';
 import JSON5 from 'json5';
@@ -42,11 +30,11 @@ import { reloadChannel } from '@/scripts/unison-reload';
 import { reactionPicker } from '@/scripts/reaction-picker';
 import { getUrlWithoutLoginId } from '@/scripts/login-id';
 import { getAccountFromId } from '@/scripts/get-account-from-id';
-import { deckStore } from './ui/deck/deck-store';
-import { miLocalStorage } from './local-storage';
-import { claimAchievement, claimedAchievements } from './scripts/achievements';
-import { fetchCustomEmojis } from './custom-emojis';
-import { mainRouter } from './router';
+import { deckStore } from '@/ui/deck/deck-store';
+import { miLocalStorage } from '@/local-storage';
+import { claimAchievement, claimedAchievements } from '@/scripts/achievements';
+import { fetchCustomEmojis } from '@/custom-emojis';
+import { mainRouter } from '@/router';
 
 console.info(`Misskey v${version}`);
 
@@ -55,7 +43,9 @@ if (_DEV_) {
 
 	console.info(`vue ${vueVersion}`);
 
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	(window as any).$i = $i;
+	// eslint-disable-next-line @typescript-eslint/no-explicit-any
 	(window as any).$store = defaultStore;
 
 	window.addEventListener('error', event => {
@@ -184,7 +174,7 @@ fetchInstanceMetaPromise.then(() => {
 
 try {
 	await fetchCustomEmojis();
-} catch (err) {}
+} catch (err) { /* empty */ }
 
 const app = createApp(
 	new URLSearchParams(window.location.search).has('zen') ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
@@ -212,20 +202,20 @@ await deckStore.ready;
 
 // https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
 // なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する
-const rootEl = (() => {
+const rootEl = ((): HTMLElement => {
 	const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
 
-	const currentEl = document.getElementById(MISSKEY_MOUNT_DIV_ID);
+	const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
 
-	if (currentEl) {
+	if (currentRoot) {
 		console.warn('multiple import detected');
-		return currentEl;
+		return currentRoot;
 	}
 
-	const rootEl = document.createElement('div');
-	rootEl.id = MISSKEY_MOUNT_DIV_ID;
-	document.body.appendChild(rootEl);
-	return rootEl;
+	const root = document.createElement('div');
+	root.id = MISSKEY_MOUNT_DIV_ID;
+	document.body.appendChild(root);
+	return root;
 })();
 
 app.mount(rootEl);
@@ -256,8 +246,7 @@ if (lastVersion !== version) {
 				popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed');
 			}
 		}
-	} catch (err) {
-	}
+	} catch (err) { /* empty */ }
 }
 
 await defaultStore.ready;
@@ -354,6 +343,16 @@ if ($i) {
 	// only add post shortcuts if logged in
 	hotkeys['p|n'] = post;
 
+	if (defaultStore.state.accountSetupWizard !== -1) {
+		// このウィザードが実装される前に登録したユーザーには表示させないため
+		// TODO: そのうち消す
+		if (Date.now() - new Date($i.createdAt).getTime() < 1000 * 60 * 60 * 24) {
+			popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed');
+		} else {
+			defaultStore.set('accountSetupWizard', -1);
+		}
+	}
+
 	if ($i.isDeleted) {
 		alert({
 			type: 'warning',
@@ -442,6 +441,10 @@ if ($i) {
 		claimAchievement('client30min');
 	}, 1000 * 60 * 30);
 
+	window.setTimeout(() => {
+		claimAchievement('client60min');
+	}, 1000 * 60 * 60);
+
 	const lastUsed = miLocalStorage.getItem('lastUsed');
 	if (lastUsed) {
 		const lastUsedDate = parseInt(lastUsed, 10);
@@ -456,7 +459,7 @@ if ($i) {
 
 	const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
 	const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
-	if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3)))) {
+	if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
 		if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
 			popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
 		}
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index 9a288f264c598376c6c6a3a8fbdc22ed8fb4028c..441a35747a3be50a9684c5547e6d234adc4542f2 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -24,6 +24,7 @@ type Keys =
 	'customCss' |
 	'message_drafts' |
 	'scratchpad' |
+	'debug' |
 	`miux:${string}` |
 	`ui:folder:${string}` |
 	`themes:${string}` |
@@ -32,7 +33,7 @@ type Keys =
 	'emojis' // DEPRECATED, stored in indexeddb (13.9.0~);
 
 export const miLocalStorage = {
-	getItem: (key: Keys) => window.localStorage.getItem(key),
-	setItem: (key: Keys, value: string) => window.localStorage.setItem(key, value),
-	removeItem: (key: Keys) => window.localStorage.removeItem(key),
+	getItem: (key: Keys): string | null => window.localStorage.getItem(key),
+	setItem: (key: Keys, value: string): void => window.localStorage.setItem(key, value),
+	removeItem: (key: Keys): void => window.localStorage.removeItem(key),
 };
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 962f9cdd98d96369a301832468380cf087990b4b..c4f9d47d7d35ba639516b877bd838e14e9888b6a 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -18,6 +18,8 @@ import MkPopupMenu from '@/components/MkPopupMenu.vue';
 import MkContextMenu from '@/components/MkContextMenu.vue';
 import { MenuItem } from '@/types/menu';
 import copyToClipboard from './scripts/copy-to-clipboard';
+import { showMovedDialog } from './scripts/show-moved-dialog';
+import { DriveFile } from 'misskey-js/built/entities';
 
 export const openingWindowsCount = ref(0);
 
@@ -55,6 +57,12 @@ export const apiWithDialog = ((
 		} else if (err.code === 'RATE_LIMIT_EXCEEDED') {
 			title = i18n.ts.cannotPerformTemporary;
 			text = i18n.ts.cannotPerformTemporaryDescription;
+		} else if (err.code === 'INVALID_PARAM') {
+			title = i18n.ts.invalidParamError;
+			text = i18n.ts.invalidParamErrorDescription;
+		} else if (err.code === 'ROLE_PERMISSION_DENIED') {
+			title = i18n.ts.permissionDeniedError;
+			text = i18n.ts.permissionDeniedErrorDescription;
 		} else if (err.code.startsWith('TOO_MANY')) {
 			title = i18n.ts.youCannotCreateAnymore;
 			text = `${i18n.ts.error}: ${err.id}`;
@@ -413,7 +421,7 @@ export async function selectUser(opts: { includeSelf?: boolean } = {}) {
 	});
 }
 
-export async function selectDriveFile(multiple: boolean) {
+export async function selectDriveFile(multiple: boolean): Promise<DriveFile[]> {
 	return new Promise((resolve, reject) => {
 		popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), {
 			type: 'file',
@@ -421,7 +429,7 @@ export async function selectDriveFile(multiple: boolean) {
 		}, {
 			done: files => {
 				if (files) {
-					resolve(multiple ? files : files[0]);
+					resolve(files);
 				}
 			},
 		}, 'closed');
@@ -572,6 +580,8 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent)
 }
 
 export function post(props: Record<string, any> = {}): Promise<void> {
+	showMovedDialog();
+
 	return new Promise((resolve, reject) => {
 		// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
 		// NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index bca4d17784b0fde760a384141ee6fd0dac584bb5..e592c629ce629f839c82d771095fee31b360071b 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -132,6 +132,18 @@ const patronsWithIcon = [{
 }, {
 	name: 'mollinaca',
 	icon: 'https://misskey-hub.net/patrons/ceb36b8f66e549bdadb3b90d5da62314.jpg',
+}, {
+	name: '坂本龍',
+	icon: 'https://misskey-hub.net/patrons/a631cf8b490145cf8dbbe4e7508cfbc2.jpg',
+}, {
+	name: 'takke',
+	icon: 'https://misskey-hub.net/patrons/6c3327e626c046f2914fbcd9f7557935.jpg',
+}, {
+	name: 'ぺんぎん',
+	icon: 'https://misskey-hub.net/patrons/6a652e0534ff4cb1836e7ce4968d76a7.jpg',
+}, {
+	name: 'かみらえっと',
+	icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
 }];
 
 const patrons = [
@@ -219,6 +231,13 @@ const patrons = [
 	'巣黒るい@リスケモ男の娘VTuber!',
 	'ふぇいぽむ',
 	'依古田イコ',
+	'戸塚こだま',
+	'すー。',
+	'秋雨/Slime-hatena.jp',
+	'けそ',
+	'ずも',
+	'binvinyl',
+	'渡志郎',
 ];
 
 let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index d4614302344632309adb5119071f71414b983748..2d82fcf2771371242922f76672559cd1366f6f59 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -53,7 +53,15 @@ function search() {
 	}
 
 	if (selectedTags.size === 0) {
-		searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
+		const queryarry = q.match(/\:([a-z0-9_]*)\:/g);
+
+		if (queryarry) {
+			searchEmojis = customEmojis.value.filter(emoji => 
+				queryarry.includes(`:${emoji.name}:`)
+			);
+		} else {
+			searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
+		}
 	} else {
 		searchEmojis = customEmojis.value.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t)));
 	}
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index d54d93eaee57fc9370226c73c6c3729c44e73c3e..8e29990426fe3eaf0a136a15661418c49461c36e 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -3,10 +3,10 @@
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer v-if="tab === 'overview'" :content-max="600" :margin-min="20">
 		<div class="_gaps_m">
-			<div class="fwhjspax" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
-				<div class="content">
-					<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" class="icon"/>
-					<div class="name">
+			<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
+				<div style="overflow: clip;">
+					<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
+					<div :class="$style.bannerName">
 						<b>{{ instance.name ?? host }}</b>
 					</div>
 				</div>
@@ -41,7 +41,14 @@
 							<template #value>{{ instance.maintainerEmail }}</template>
 						</MkKeyValue>
 					</FormSplit>
-					<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.tos }}</FormLink>
+					<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>
 				</div>
 			</FormSection>
 
@@ -94,6 +101,7 @@ import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormSplit from '@/components/form/split.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkInstanceStats from '@/components/MkInstanceStats.vue';
 import * as os from '@/os';
@@ -148,31 +156,63 @@ definePageMetadata(computed(() => ({
 })));
 </script>
 
-<style lang="scss" scoped>
-.fwhjspax {
+<style lang="scss" module>
+.banner {
 	text-align: center;
 	border-radius: 10px;
 	overflow: clip;
 	background-size: cover;
 	background-position: center center;
+}
+
+.bannerIcon {
+	display: block;
+	margin: 16px auto 0 auto;
+	height: 64px;
+	border-radius: 8px;
+}
+
+.bannerName {
+	display: block;
+	padding: 16px;
+	color: #fff;
+	text-shadow: 0 0 8px #000;
+	background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
+}
 
-	> .content {
-		overflow: hidden;
-
-		> .icon {
-			display: block;
-			margin: 16px auto 0 auto;
-			height: 64px;
-			border-radius: 8px;
-		}
-
-		> .name {
-			display: block;
-			padding: 16px;
-			color: #fff;
-			text-shadow: 0 0 8px #000;
-			background: linear-gradient(transparent, rgba(0, 0, 0, 0.7));
-		}
+.rules {
+	counter-reset: item;
+	list-style: none;
+	padding: 0;
+	margin: 0;
+}
+
+.rule {
+	display: flex;
+	gap: 8px;
+	word-break: break-word;
+
+	&::before {
+		flex-shrink: 0;
+		display: flex;
+		position: sticky;
+		top: calc(var(--stickyTop, 0px) + 8px);
+		counter-increment: item;
+		content: counter(item);
+		width: 32px;
+		height: 32px;
+		line-height: 32px;
+		background-color: var(--accentedBg);
+		color: var(--accent);
+		font-size: 13px;
+		font-weight: bold;
+		align-items: center;
+		justify-content: center;
+		border-radius: 999px;
 	}
 }
+
+.ruleText {
+	padding-top: 6px;
+}
 </style>
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index b742132af6b70163d2cdc8eeb920f6a9040dd20f..d51bf6230a9a02d47332d04dd3eea943a92980a2 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -96,7 +96,9 @@ async function testEmail() {
 	const { canceled, result: destination } = await os.inputText({
 		title: i18n.ts.destination,
 		type: 'email',
-		placeholder: instance.maintainerEmail,
+		default: instance.maintainerEmail ?? '',
+		placeholder: 'test@example.com',
+		minLength: 1,
 	});
 	if (canceled) return;
 	os.apiWithDialog('admin/send-email', {
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index ebe1a8ade098cc6847b54249545fee1f3ca1c5b3..ffd3b6e233c605ada252a02ac7a577b97c2bc983 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -5,14 +5,30 @@
 		<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
 			<FormSuspense :p="init">
 				<div class="_gaps_m">
-					<FormSection first>
-						<div class="_gaps_m">
-							<MkTextarea v-model="sensitiveWords">
-								<template #label>{{ i18n.ts.sensitiveWords }}</template>
-								<template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template>
-							</MkTextarea>
-						</div>
-					</FormSection>
+					<MkSwitch v-model="enableRegistration">
+						<template #label>{{ i18n.ts.enableRegistration }}</template>
+					</MkSwitch>
+
+					<MkSwitch v-model="emailRequiredForSignup">
+						<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
+					</MkSwitch>
+
+					<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
+
+					<MkInput v-model="tosUrl">
+						<template #prefix><i class="ti ti-link"></i></template>
+						<template #label>{{ i18n.ts.tosUrl }}</template>
+					</MkInput>
+
+					<MkTextarea v-model="preservedUsernames">
+						<template #label>{{ i18n.ts.preservedUsernames }}</template>
+						<template #caption>{{ i18n.ts.preservedUsernamesDescription }}</template>
+					</MkTextarea>
+					
+					<MkTextarea v-model="sensitiveWords">
+						<template #label>{{ i18n.ts.sensitiveWords }}</template>
+						<template #caption>{{ i18n.ts.sensitiveWordsDescription }}</template>
+					</MkTextarea>
 				</div>
 			</FormSuspense>
 		</MkSpacer>
@@ -41,17 +57,30 @@ import { fetchInstance } from '@/instance';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import MkButton from '@/components/MkButton.vue';
+import FormLink from '@/components/form/link.vue';
 
+let enableRegistration: boolean = $ref(false);
+let emailRequiredForSignup: boolean = $ref(false);
 let sensitiveWords: string = $ref('');
+let preservedUsernames: string = $ref('');
+let tosUrl: string | null = $ref(null);
 
 async function init() {
 	const meta = await os.api('admin/meta');
+	enableRegistration = !meta.disableRegistration;
+	emailRequiredForSignup = meta.emailRequiredForSignup;
 	sensitiveWords = meta.sensitiveWords.join('\n');
+	preservedUsernames = meta.preservedUsernames.join('\n');
+	tosUrl = meta.tosUrl;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
+		disableRegistration: !enableRegistration,
+		emailRequiredForSignup,
+		tosUrl,
 		sensitiveWords: sensitiveWords.split('\n'),
+		preservedUsernames: preservedUsernames.split('\n'),
 	}).then(() => {
 		fetchInstance();
 	});
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index b1aa03f1f74f23a366a8b03d297f114d4c46cfc1..c211ef2f055f1f73f79c2c2596066c16121d1baf 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -54,6 +54,7 @@ if (props.id) {
 		target: 'manual',
 		condFormula: { id: uuid(), type: 'isRemote' },
 		isPublic: false,
+		isExplorable: false,
 		asBadge: false,
 		canEditMembersByModerator: false,
 		displayOrder: 0,
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 873ff02feb2222f764a94dec0c38294eefa35d27..49942c87ce58aec4531441e5b90674f294768530 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -8,10 +8,9 @@
 		<template #label>{{ i18n.ts._role.description }}</template>
 	</MkTextarea>
 
-	<MkInput v-model="role.color">
+	<MkColorInput v-model="role.color">
 		<template #label>{{ i18n.ts.color }}</template>
-		<template #caption>#RRGGBB</template>
-	</MkInput>
+	</MkColorInput>
 
 	<MkInput v-model="role.iconUrl">
 		<template #label>{{ i18n.ts._role.iconUrl }}</template>
@@ -59,6 +58,11 @@
 		<template #caption>{{ i18n.ts._role.descriptionOfAsBadge }}</template>
 	</MkSwitch>
 
+	<MkSwitch v-model="role.isExplorable" :readonly="readonly">
+		<template #label>{{ i18n.ts._role.isExplorable }}</template>
+		<template #caption>{{ i18n.ts._role.descriptionOfIsExplorable }}</template>
+	</MkSwitch>
+
 	<FormSlot>
 		<template #label><i class="ti ti-license"></i> {{ i18n.ts._role.policies }}</template>
 		<div class="_gaps_s">
@@ -206,7 +210,7 @@
 					</MkRange>
 				</div>
 			</MkFolder>
-
+			
 			<MkFolder v-if="matchQuery([i18n.ts._role._options.driveCapacity, 'driveCapacityMb'])">
 				<template #label>{{ i18n.ts._role._options.driveCapacity }}</template>
 				<template #suffix>
@@ -227,6 +231,26 @@
 				</div>
 			</MkFolder>
 
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])">
+				<template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template>
+				<template #suffix>
+					<span v-if="role.policies.alwaysMarkNsfw.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.alwaysMarkNsfw.value ? i18n.ts.yes : i18n.ts.no }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.alwaysMarkNsfw)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkSwitch v-model="role.policies.alwaysMarkNsfw.value" :disabled="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts.enable }}</template>
+					</MkSwitch>
+					<MkRange v-model="role.policies.alwaysMarkNsfw.priority" :min="0" :max="2" :step="1" easing :text-converter="(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.pinMax, 'pinLimit'])">
 				<template #label>{{ i18n.ts._role._options.pinMax }}</template>
 				<template #suffix>
@@ -409,6 +433,7 @@ import { watch } from 'vue';
 import { throttle } from 'throttle-debounce';
 import RolesEditorFormula from './RolesEditorFormula.vue';
 import MkInput from '@/components/MkInput.vue';
+import MkColorInput from '@/components/MkColorInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -475,6 +500,7 @@ const save = throttle(100, () => {
 		isAdministrator: role.isAdministrator,
 		isModerator: role.isModerator,
 		isPublic: role.isPublic,
+		isExplorable: role.isExplorable,
 		asBadge: role.asBadge,
 		canEditMembersByModerator: role.canEditMembersByModerator,
 		policies: role.policies,
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index a1e467edbdf08f628d6973d9c63ad85429de8924..e8dbe1c5f0ed6c16fb3d557266c899d227c2fd91 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -75,6 +75,14 @@
 							</MkInput>
 						</MkFolder>
 
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.alwaysMarkNsfw, 'alwaysMarkNsfw'])">
+							<template #label>{{ i18n.ts._role._options.alwaysMarkNsfw }}</template>
+							<template #suffix>{{ policies.alwaysMarkNsfw ? i18n.ts.yes : i18n.ts.no }}</template>
+							<MkSwitch v-model="policies.alwaysMarkNsfw">
+								<template #label>{{ i18n.ts.enable }}</template>
+							</MkSwitch>
+						</MkFolder>
+
 						<MkFolder v-if="matchQuery([i18n.ts._role._options.pinMax, 'pinLimit'])">
 							<template #label>{{ i18n.ts._role._options.pinMax }}</template>
 							<template #suffix>{{ policies.pinLimit }}</template>
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
new file mode 100644
index 0000000000000000000000000000000000000000..85781c0bd00cd128a503f2b0d922dd7d964fa398
--- /dev/null
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -0,0 +1,128 @@
+<template>
+<div>
+	<MkStickyContainer>
+		<template #header><XHeader :tabs="headerTabs"/></template>
+		<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+			<div class="_gaps_m">
+				<div>{{ i18n.ts._serverRules.description }}</div>
+				<Sortable
+					v-model="serverRules"
+					class="_gaps_m"
+					:item-key="(_, i) => i"
+					:animation="150"
+					:handle="'.' + $style.itemHandle"
+					@start="e => e.item.classList.add('active')"
+					@end="e => e.item.classList.remove('active')"
+				>
+					<template #item="{element,index}">
+						<div :class="$style.item">
+							<div :class="$style.itemHeader">
+								<div :class="$style.itemNumber" v-text="String(index + 1)"/>
+								<span :class="$style.itemHandle"><i class="ti ti-menu"/></span>
+								<button class="_button" :class="$style.itemRemove" @click="remove(index)"><i class="ti ti-x"></i></button>
+							</div>
+							<MkInput v-model="serverRules[index]"/>
+						</div>
+					</template>
+				</Sortable>
+				<div :class="$style.commands">
+					<MkButton rounded @click="serverRules.push('')"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+					<MkButton primary rounded :class="$style.buttonSave" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+				</div>
+			</div>
+		</MkSpacer>
+	</MkStickyContainer>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
+import XHeader from './_header_.vue';
+import * as os from '@/os';
+import { fetchInstance, instance } from '@/instance';
+import { i18n } from '@/i18n';
+import { definePageMetadata } from '@/scripts/page-metadata';
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
+
+let serverRules: string[] = $ref(instance.serverRules);
+
+const save = async () => {
+	await os.apiWithDialog('admin/update-meta', {
+		serverRules,
+	});
+	fetchInstance();
+};
+
+const remove = (index: number): void => {
+	serverRules.splice(index, 1);
+};
+
+const headerTabs = $computed(() => []);
+
+definePageMetadata({
+	title: i18n.ts.serverRules,
+	icon: 'ti ti-checkbox',
+});
+</script>
+
+<style lang="scss" module>
+.item {
+	display: block;
+	color: var(--navFg);
+}
+
+.itemHeader {
+	display: flex;
+	margin-bottom: 8px;
+	align-items: center;
+}
+
+.itemHandle {
+	display: flex;
+	width: 40px;
+	height: 40px;
+	align-items: center;
+	justify-content: center;
+	cursor: move;
+}
+
+.itemNumber {
+	display: flex;
+	background-color: var(--accentedBg);
+	color: var(--accent);
+	font-size: 14px;
+	font-weight: bold;
+	width: 28px;
+	height: 28px;
+	align-items: center;
+	justify-content: center;
+	border-radius: 999px;
+	margin-right: 8px;
+}
+
+.itemEdit {
+	width: 100%;
+	max-width: 100%;
+	min-width: 100%;
+}
+
+.itemRemove {
+	width: 40px;
+	height: 40px;
+	color: var(--error);
+	margin-left: auto;
+	border-radius: 6px;
+
+	&:hover {
+		background: var(--X5);
+	}
+}
+
+.commands {
+	display: flex;
+	gap: 16px;
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 65e64930d5261792f1f3b03d7db98c1d50c88e72..7ec3c381f3a33815a24a4330b0d0c8d2db093c6f 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -13,11 +13,6 @@
 						<template #label>{{ i18n.ts.instanceDescription }}</template>
 					</MkTextarea>
 
-					<MkInput v-model="tosUrl">
-						<template #prefix><i class="ti ti-link"></i></template>
-						<template #label>{{ i18n.ts.tosUrl }}</template>
-					</MkInput>
-
 					<FormSplit :min-width="300">
 						<MkInput v-model="maintainerName">
 							<template #label>{{ i18n.ts.maintainerName }}</template>
@@ -36,14 +31,6 @@
 
 					<FormSection>
 						<div class="_gaps_s">
-							<MkSwitch v-model="enableRegistration">
-								<template #label>{{ i18n.ts.enableRegistration }}</template>
-							</MkSwitch>
-
-							<MkSwitch v-model="emailRequiredForSignup">
-								<template #label>{{ i18n.ts.emailRequiredForSignup }}</template>
-							</MkSwitch>
-
 							<MkSwitch v-model="enableChartsForRemoteUser">
 								<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
 							</MkSwitch>
@@ -73,11 +60,9 @@
 								<template #label>{{ i18n.ts.backgroundImageUrl }}</template>
 							</MkInput>
 
-							<MkInput v-model="themeColor">
-								<template #prefix><i class="ti ti-palette"></i></template>
+							<MkColorInput v-model="themeColor">
 								<template #label>{{ i18n.ts.themeColor }}</template>
-								<template #caption>#RRGGBB</template>
-							</MkInput>
+							</MkColorInput>
 
 							<MkTextarea v-model="defaultLightTheme">
 								<template #label>{{ i18n.ts.instanceDefaultLightTheme }}</template>
@@ -166,10 +151,10 @@ import { fetchInstance } from '@/instance';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import MkButton from '@/components/MkButton.vue';
+import MkColorInput from '@/components/MkColorInput.vue';
 
 let name: string | null = $ref(null);
 let description: string | null = $ref(null);
-let tosUrl: string | null = $ref(null);
 let maintainerName: string | null = $ref(null);
 let maintainerEmail: string | null = $ref(null);
 let iconUrl: string | null = $ref(null);
@@ -180,8 +165,6 @@ let defaultLightTheme: any = $ref(null);
 let defaultDarkTheme: any = $ref(null);
 let pinnedUsers: string = $ref('');
 let cacheRemoteFiles: boolean = $ref(false);
-let enableRegistration: boolean = $ref(false);
-let emailRequiredForSignup: boolean = $ref(false);
 let enableServiceWorker: boolean = $ref(false);
 let enableChartsForRemoteUser: boolean = $ref(false);
 let enableChartsForFederatedInstances: boolean = $ref(false);
@@ -194,7 +177,6 @@ async function init() {
 	const meta = await os.api('admin/meta');
 	name = meta.name;
 	description = meta.description;
-	tosUrl = meta.tosUrl;
 	iconUrl = meta.iconUrl;
 	bannerUrl = meta.bannerUrl;
 	backgroundImageUrl = meta.backgroundImageUrl;
@@ -205,8 +187,6 @@ async function init() {
 	maintainerEmail = meta.maintainerEmail;
 	pinnedUsers = meta.pinnedUsers.join('\n');
 	cacheRemoteFiles = meta.cacheRemoteFiles;
-	enableRegistration = !meta.disableRegistration;
-	emailRequiredForSignup = meta.emailRequiredForSignup;
 	enableServiceWorker = meta.enableServiceWorker;
 	enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
 	enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
@@ -220,7 +200,6 @@ function save() {
 	os.apiWithDialog('admin/update-meta', {
 		name,
 		description,
-		tosUrl,
 		iconUrl,
 		bannerUrl,
 		backgroundImageUrl,
@@ -231,8 +210,6 @@ function save() {
 		maintainerEmail,
 		pinnedUsers: pinnedUsers.split('\n'),
 		cacheRemoteFiles,
-		disableRegistration: !enableRegistration,
-		emailRequiredForSignup,
 		enableServiceWorker,
 		enableChartsForRemoteUser,
 		enableChartsForFederatedInstances,
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 9cb440d2bb5c5846aab4a5aa812ade64a358b298..a74ab40473a5b6e05be491db1a85fbcf0c28ef8d 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -11,6 +11,10 @@
 				<template #label>{{ i18n.ts.description }}</template>
 			</MkTextarea>
 
+			<MkColorInput v-model="color">
+				<template #label>{{ i18n.ts.color }}</template>
+			</MkColorInput>
+
 			<div>
 				<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="ti ti-plus"></i> {{ i18n.ts._channel.setBanner }}</MkButton>
 				<div v-else-if="bannerUrl">
@@ -42,8 +46,9 @@
 				</div>
 			</MkFolder>
 
-			<div>
+			<div class="_buttons">
 				<MkButton primary @click="save()"><i class="ti ti-device-floppy"></i> {{ channelId ? i18n.ts.save : i18n.ts.create }}</MkButton>
+				<MkButton v-if="channelId" danger @click="archive()"><i class="ti ti-trash"></i> {{ i18n.ts.archive }}</MkButton>
 			</div>
 		</div>
 	</MkSpacer>
@@ -55,6 +60,7 @@ import { computed, ref, watch, defineAsyncComponent } from 'vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
+import MkColorInput from '@/components/MkColorInput.vue';
 import { selectFile } from '@/scripts/select-file';
 import * as os from '@/os';
 import { useRouter } from '@/router';
@@ -75,6 +81,7 @@ let name = $ref(null);
 let description = $ref(null);
 let bannerUrl = $ref<string | null>(null);
 let bannerId = $ref<string | null>(null);
+let color = $ref('#000');
 const pinnedNotes = ref([]);
 
 watch(() => bannerId, async () => {
@@ -101,6 +108,7 @@ async function fetchChannel() {
 	pinnedNotes.value = channel.pinnedNoteIds.map(id => ({
 		id,
 	}));
+	color = channel.color;
 }
 
 fetchChannel();
@@ -128,6 +136,7 @@ function save() {
 		description: description,
 		bannerId: bannerId,
 		pinnedNoteIds: pinnedNotes.value.map(x => x.id),
+		color: color,
 	};
 
 	if (props.channelId) {
@@ -143,6 +152,23 @@ function save() {
 	}
 }
 
+async function archive() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		title: i18n.t('channelArchiveConfirmTitle', { name: name }),
+		text: i18n.ts.channelArchiveConfirmDescription,
+	});
+
+	if (canceled) return;
+	
+	os.api('channels/update', {
+		channelId: props.channelId,
+		isArchived: true,
+	}).then(() => {
+		os.success();
+	});
+}
+
 function setBannerImage(evt) {
 	selectFile(evt.currentTarget ?? evt.target, null).then(file => {
 		bannerId = file.id;
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 437c1fae31b9bd4532cf300df8b10873037f225d..af1b4d2056eec41e6d89833c7d7b9f34ace6028c 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -28,6 +28,8 @@
 			</MkFoldableSection>
 		</div>
 		<div v-if="channel && tab === 'timeline'" class="_gaps">
+			<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
+
 			<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
 			<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
 
@@ -36,6 +38,17 @@
 		<div v-else-if="tab === 'featured'">
 			<MkNotes :pagination="featuredPagination"/>
 		</div>
+		<div v-else-if="tab === 'search'">
+			<div class="_gaps">
+				<div>
+					<MkInput v-model="searchQuery">
+						<template #prefix><i class="ti ti-search"></i></template>
+					</MkInput>
+					<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
+				</div>
+				<MkNotes v-if="searchPagination" :key="searchQuery" :pagination="searchPagination"/>
+			</div>
+		</div>
 	</MkSpacer>
 	<template #footer>
 		<div :class="$style.footer">
@@ -63,8 +76,10 @@ import { deviceKind } from '@/scripts/device-kind';
 import MkNotes from '@/components/MkNotes.vue';
 import { url } from '@/config';
 import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
 import { defaultStore } from '@/store';
 import MkNote from '@/components/MkNote.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 
 const router = useRouter();
@@ -76,6 +91,8 @@ const props = defineProps<{
 let tab = $ref('timeline');
 let channel = $ref(null);
 let favorited = $ref(false);
+let searchQuery = $ref('');
+let searchPagination = $ref();
 const featuredPagination = $computed(() => ({
 	endpoint: 'notes/featured' as const,
 	limit: 10,
@@ -123,6 +140,21 @@ async function unfavorite() {
 	});
 }
 
+async function search() {
+	const query = searchQuery.toString().trim();
+
+	if (query == null) return;
+
+	searchPagination = {
+		endpoint: 'notes/search',
+		limit: 10,
+		params: {
+			query: searchQuery,
+			channelId: channel.id,
+		},
+	};
+}
+
 const headerActions = $computed(() => {
 	if (channel && channel.userId) {
 		const share = {
@@ -160,6 +192,10 @@ const headerTabs = $computed(() => [{
 	key: 'featured',
 	title: i18n.ts.featured,
 	icon: 'ti ti-bolt',
+}, {
+	key: 'search',
+	title: i18n.ts.search,
+	icon: 'ti ti-search',
 }]);
 
 definePageMetadata(computed(() => channel ? {
@@ -170,7 +206,7 @@ definePageMetadata(computed(() => channel ? {
 
 <style lang="scss" module>
 .main {
-	min-height: calc(var(--containerHeight) - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+	min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
 }
 
 .footer {
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 70e7705d1d3ed10e2889610ca6b29b59135649be..e670cdd864787e89bffa4121d0e1b42c62e7a77f 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -96,7 +96,7 @@ const ownedPagination = {
 async function search() {
 	const query = searchQuery.toString().trim();
 
-	if (query == null || query === '') return;
+	if (query == null) return;
 
 	const type = searchType.toString().trim();
 
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index 59cb3262b785b7bae2aea6c6592bf4927e7b5a56..3f13f0787d6a0d17ee8b2bea9aff448b156eb277 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -15,9 +15,10 @@
 					<div v-if="selectMode" class="_buttons">
 						<MkButton inline @click="selectAll">Select all</MkButton>
 						<MkButton inline @click="setCategoryBulk">Set category</MkButton>
+						<MkButton inline @click="setTagBulk">Set tag</MkButton>
 						<MkButton inline @click="addTagBulk">Add tag</MkButton>
 						<MkButton inline @click="removeTagBulk">Remove tag</MkButton>
-						<MkButton inline @click="setTagBulk">Set tag</MkButton>
+						<MkButton inline @click="setLisenceBulk">Set Lisence</MkButton>
 						<MkButton inline danger @click="delBulk">Delete</MkButton>
 					</div>
 					<MkPagination ref="emojisPaginationComponent" :pagination="pagination">
@@ -221,6 +222,18 @@ const setCategoryBulk = async () => {
 	emojisPaginationComponent.value.reload();
 };
 
+const setLisenceBulk = async () => {
+	const { canceled, result } = await os.inputText({
+		title: 'License',
+	});
+	if (canceled) return;
+	await os.apiWithDialog('admin/emoji/set-license-bulk', {
+		ids: selectedEmojis.value,
+		license: result,
+	});
+	emojisPaginationComponent.value.reload();
+};
+
 const addTagBulk = async () => {
 	const { canceled, result } = await os.inputText({
 		title: 'Tag',
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index 35edcc7cdad0af2bd30ed3f9dcd9cbf2db56f4f5..816825e5b6e5649d7062cb20e2939196664a8b87 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -33,7 +33,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import MkInput from '@/components/MkInput.vue';
 import { useRouter } from '@/router';
 
-const PRESET_DEFAULT = `/// @ 0.13.1
+const PRESET_DEFAULT = `/// @ 0.13.2
 
 var name = ""
 
@@ -51,7 +51,7 @@ Ui:render([
 ])
 `;
 
-const PRESET_OMIKUJI = `/// @ 0.13.1
+const PRESET_OMIKUJI = `/// @ 0.13.2
 // ユーザーごとに日替わりのおみくじのプリセット
 
 // 選択肢
@@ -94,7 +94,7 @@ Ui:render([
 ])
 `;
 
-const PRESET_SHUFFLE = `/// @ 0.13.1
+const PRESET_SHUFFLE = `/// @ 0.13.2
 // 巻き戻し可能な文字シャッフルのプリセット
 
 let string = "ペペロンチーノ"
@@ -173,7 +173,7 @@ var cursor = 0
 do()
 `;
 
-const PRESET_QUIZ = `/// @ 0.13.1
+const PRESET_QUIZ = `/// @ 0.13.2
 let title = '地理クイズ'
 
 let qas = [{
@@ -286,7 +286,7 @@ qaEls.push(Ui:C:container({
 Ui:render(qaEls)
 `;
 
-const PRESET_TIMELINE = `/// @ 0.13.1
+const PRESET_TIMELINE = `/// @ 0.13.2
 // APIリクエストを行いローカルタイムラインを表示するプリセット
 
 @fetch() {
@@ -305,6 +305,11 @@ const PRESET_TIMELINE = `/// @ 0.13.1
 	// それぞれのノートごとにUI要素作成
 	let noteEls = []
 	each (let note, notes) {
+		// 表示名を設定していないアカウントはidを表示
+		let userName = if Core:type(note.user.name) == "str" note.user.name else note.user.username
+		// リノートもしくはメディア・投票のみで本文が無いノートに代替表示文を設定
+		let noteText = if Core:type(note.text) == "str" note.text else "(リノートもしくはメディア・投票のみのノート)"
+
 		let el = Ui:C:container({
 			bgColor: "#444"
 			fgColor: "#fff"
@@ -312,11 +317,11 @@ const PRESET_TIMELINE = `/// @ 0.13.1
 			rounded: true
 			children: [
 				Ui:C:mfm({
-					text: note.user.name
+					text: userName
 					bold: true
 				})
 				Ui:C:mfm({
-					text: note.text
+					text: noteText
 				})
 			]
 		})
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index 1fae7686e5f95fbbc8976f110eac81a98bbdea21..cafcee0c33981e76337557dab26c8a8f45b760d8 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
-		<FormSuspense :p="init">
+		<FormSuspense :p="init" class="_gaps">
 			<MkInput v-model="title">
 				<template #label>{{ i18n.ts.title }}</template>
 			</MkInput>
@@ -11,7 +11,7 @@
 				<template #label>{{ i18n.ts.description }}</template>
 			</MkTextarea>
 
-			<div class="">
+			<div class="_gaps_s">
 				<div v-for="file in files" :key="file.id" class="wqugxsfx" :style="{ backgroundImage: file ? `url(${ file.thumbnailUrl })` : null }">
 					<div class="name">{{ file.name }}</div>
 					<button v-tooltip="i18n.ts.remove" class="remove _button" @click="remove(file)"><i class="ti ti-x"></i></button>
@@ -21,10 +21,12 @@
 
 			<MkSwitch v-model="isSensitive">{{ i18n.ts.markAsSensitive }}</MkSwitch>
 
-			<MkButton v-if="postId" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
-			<MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton>
+			<div class="_buttons">
+				<MkButton v-if="postId" primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+				<MkButton v-else primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.publish }}</MkButton>
 
-			<MkButton v-if="postId" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+				<MkButton v-if="postId" danger @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+			</div>
 		</FormSuspense>
 	</MkSpacer>
 </MkStickyContainer>
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 768a48746c95c3dd965f7d9472a5a6c347da5e24..86201e8e0c87748b8e97c0d69fdeac9f7375c90b 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -131,7 +131,7 @@ definePageMetadata(computed(() => list ? {
 
 <style lang="scss" module>
 .main {
-	min-height: calc(var(--containerHeight) - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+	min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
 }
 
 .userItem {
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
index ffeb8ba285c8c544727fdc4ac825fbc8d161b2ec..e97a4b07f121d8cc97592d0732fd2855e866efbf 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
@@ -33,8 +33,8 @@ const emit = defineEmits<{
 let file: any = $ref(null);
 
 async function choose() {
-	os.selectDriveFile(false).then((fileResponse: any) => {
-		file = fileResponse;
+	os.selectDriveFile(false).then((fileResponse) => {
+		file = fileResponse[0];
 		emit('update:modelValue', {
 			...props.modelValue,
 			fileId: fileResponse.id,
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index f2645394a2aab6df82db846b283f66ea96b64ce1..fe39c594ba868b10ac348e49607a70718fa4f2e4 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -1,8 +1,16 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
-
-	<MkSpacer v-if="tab === 'users'" :content-max="1200">
+	<MKSpacer v-if="!(typeof error === 'undefined')" :content-max="1200">
+		<div :class="$style.root">
+			<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
+			<p :class="$style.text">
+				<i class="ti ti-alert-triangle"></i>
+				{{ error }}
+			</p>
+		</div>
+	</MKSpacer>
+	<MkSpacer v-else-if="tab === 'users'" :content-max="1200">
 		<div class="_gaps_s">
 			<div v-if="role">{{ role.description }}</div>
 			<MkUserList :pagination="users" :extractor="(item) => item.user"/>
@@ -13,7 +21,6 @@
 	</MkSpacer>
 </MkStickyContainer>
 </template>
-
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
 import * as os from '@/os';
@@ -21,6 +28,7 @@ import MkUserList from '@/components/MkUserList.vue';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
 import MkTimeline from '@/components/MkTimeline.vue';
+import { instanceName } from '@/config';
 
 const props = withDefaults(defineProps<{
 	role: string;
@@ -31,12 +39,21 @@ const props = withDefaults(defineProps<{
 
 let tab = $ref(props.initialTab);
 let role = $ref();
+let error = $ref();
 
 watch(() => props.role, () => {
 	os.api('roles/show', {
 		roleId: props.role,
 	}).then(res => {
 		role = res;
+		document.title = `${role?.name} | ${instanceName}`;
+	}).catch((err) => {
+		if (err.code === 'NO_SUCH_ROLE') {
+			error = i18n.ts.noRole;
+		} else {
+			error = i18n.ts.somethingHappened;
+		}
+		document.title = `${error} | ${instanceName}`;
 	});
 }, { immediate: true });
 
@@ -63,4 +80,23 @@ definePageMetadata(computed(() => ({
 	icon: 'ti ti-badge',
 })));
 </script>
+<style lang="scss" module>
+.root {
+	padding: 32px;
+	text-align: center;
+  align-items: center;
+}
+
+.text {
+	margin: 0 0 8px 0;
+}
+
+.img {
+	vertical-align: bottom;
+  width: 128px;
+	height: 128px;
+	margin-bottom: 16px;
+	border-radius: 16px;
+}
+</style>
 
diff --git a/packages/frontend/src/pages/settings/account-info.vue b/packages/frontend/src/pages/settings/account-stats.vue
similarity index 94%
rename from packages/frontend/src/pages/settings/account-info.vue
rename to packages/frontend/src/pages/settings/account-stats.vue
index 584808b0b4cdcadeebd0b91848f55810c4f6788d..a0f1541b401682b30311085d7bad7bbabff9560e 100644
--- a/packages/frontend/src/pages/settings/account-info.vue
+++ b/packages/frontend/src/pages/settings/account-stats.vue
@@ -1,18 +1,6 @@
 <template>
 <div class="_gaps_m">
-	<MkKeyValue>
-		<template #key>ID</template>
-		<template #value><span class="_monospace">{{ $i.id }}</span></template>
-	</MkKeyValue>
-
-	<FormSection>
-		<MkKeyValue>
-			<template #key>{{ i18n.ts.registeredDate }}</template>
-			<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
-		</MkKeyValue>
-	</FormSection>
-
-	<FormSection v-if="stats">
+	<FormSection v-if="stats" first>
 		<template #label>{{ i18n.ts.statistics }}</template>
 		<MkKeyValue oneline style="margin: 1em 0;">
 			<template #key>{{ i18n.ts.notesCount }}</template>
diff --git a/packages/frontend/src/pages/settings/delete-account.vue b/packages/frontend/src/pages/settings/delete-account.vue
deleted file mode 100644
index c6e79165c5aee970f9fad58d32fe3cb241e6e5ac..0000000000000000000000000000000000000000
--- a/packages/frontend/src/pages/settings/delete-account.vue
+++ /dev/null
@@ -1,52 +0,0 @@
-<template>
-<div class="_gaps_m">
-	<FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
-	<FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
-	<MkButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</MkButton>
-	<MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
-</div>
-</template>
-
-<script lang="ts" setup>
-import FormInfo from '@/components/MkInfo.vue';
-import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os';
-import { signout, $i } from '@/account';
-import { i18n } from '@/i18n';
-import { definePageMetadata } from '@/scripts/page-metadata';
-
-async function deleteAccount() {
-	{
-		const { canceled } = await os.confirm({
-			type: 'warning',
-			text: i18n.ts.deleteAccountConfirm,
-		});
-		if (canceled) return;
-	}
-
-	const { canceled, result: password } = await os.inputText({
-		title: i18n.ts.password,
-		type: 'password',
-	});
-	if (canceled) return;
-
-	await os.apiWithDialog('i/delete-account', {
-		password: password,
-	});
-
-	await os.alert({
-		title: i18n.ts._accountDelete.started,
-	});
-
-	await signout();
-}
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata({
-	title: i18n.ts._accountDelete.accountDelete,
-	icon: 'ti ti-alert-triangle',
-});
-</script>
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index d3fb422e01d41bc5c8039b7f46eac4f9a818a8cd..73c2b2e6040e44c6b2c7a6418f47e0539131bd33 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -119,6 +119,13 @@ function saveProfile() {
 	os.api('i/update', {
 		alwaysMarkNsfw: !!alwaysMarkNsfw,
 		autoSensitive: !!autoSensitive,
+	}).catch(err => {
+		os.alert({
+			type: 'error',
+			title: i18n.ts.error,
+			text: err.message,
+		});
+		alwaysMarkNsfw = true;
 	});
 }
 
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 904fd3f95291fbcc8bf2422d5d2ded54d78e41ee..ba0f3274fc827826b7efac411bb189c1134d715a 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -20,24 +20,71 @@
 		<option value="desktop"><i class="ti ti-device-desktop"/> {{ i18n.ts.desktop }}</option>
 	</MkRadios>
 
-	<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
-	<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
+	<FormSection>
+		<div class="_gaps_s">
+			<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
+			<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
+		</div>
+	</FormSection>
 
 	<FormSection>
-		<template #label>{{ i18n.ts.behavior }}</template>
+		<template #label>{{ i18n.ts.displayOfNote }}</template>
 
 		<div class="_gaps_m">
 			<div class="_gaps_s">
-				<MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
-				<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
+				<MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
+				<MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
+				<MkSwitch v-model="largeNoteReactions">{{ i18n.ts.largeNoteReactions }}</MkSwitch>
+				<MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch>
+				<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
+				<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
+				<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
+				<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
 				<MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
 			</div>
-			<MkSelect v-model="serverDisconnectedBehavior">
-				<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
-				<option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option>
-				<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
-				<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
+
+			<MkSelect v-model="instanceTicker">
+				<template #label>{{ i18n.ts.instanceTicker }}</template>
+				<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
+				<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
+				<option value="always">{{ i18n.ts._instanceTicker.always }}</option>
 			</MkSelect>
+
+			<MkSelect v-model="nsfw">
+				<template #label>{{ i18n.ts.nsfw }}</template>
+				<option value="respect">{{ i18n.ts._nsfw.respect }}</option>
+				<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
+				<option value="force">{{ i18n.ts._nsfw.force }}</option>
+			</MkSelect>
+		<!--
+			<MkRadios v-model="mediaListWithOneImageAppearance">
+				<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
+				<option value="expand">{{ i18n.ts.default }}</option>
+				<option value="16_9">{{ i18n.t('limitTo', { x: '16:9' }) }}</option>
+				<option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option>
+				<option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option>
+			</MkRadios>
+		-->
+		</div>
+	</FormSection>
+
+	<FormSection>
+		<template #label>{{ i18n.ts.notificationDisplay }}</template>
+
+		<div class="_gaps_m">
+			<MkRadios v-model="notificationPosition">
+				<template #label>{{ i18n.ts.position }}</template>
+				<option value="leftTop"><i class="ti ti-align-box-left-top"></i> {{ i18n.ts.leftTop }}</option>
+				<option value="rightTop"><i class="ti ti-align-box-right-top"></i> {{ i18n.ts.rightTop }}</option>
+				<option value="leftBottom"><i class="ti ti-align-box-left-bottom"></i> {{ i18n.ts.leftBottom }}</option>
+				<option value="rightBottom"><i class="ti ti-align-box-right-bottom"></i> {{ i18n.ts.rightBottom }}</option>
+			</MkRadios>
+
+			<MkRadios v-model="notificationStackAxis">
+				<template #label>{{ i18n.ts.stackAxis }}</template>
+				<option value="vertical"><i class="ti ti-carousel-vertical"></i> {{ i18n.ts.vertical }}</option>
+				<option value="horizontal"><i class="ti ti-carousel-horizontal"></i> {{ i18n.ts.horizontal }}</option>
+			</MkRadios>
 		</div>
 	</FormSection>
 
@@ -46,22 +93,15 @@
 
 		<div class="_gaps_m">
 			<div class="_gaps_s">
-				<MkSwitch v-model="showNoteActionsOnlyHover">{{ i18n.ts.showNoteActionsOnlyHover }}</MkSwitch>
-				<MkSwitch v-model="showClipButtonInNoteFooter">{{ i18n.ts.showClipButtonInNoteFooter }}</MkSwitch>
-				<MkSwitch v-model="largeNoteReactions">{{ i18n.ts.largeNoteReactions }}</MkSwitch>
-				<MkSwitch v-model="collapseRenotes">{{ i18n.ts.collapseRenotes }}</MkSwitch>
-				<MkSwitch v-model="advancedMfm">{{ i18n.ts.enableAdvancedMfm }}</MkSwitch>
-				<MkSwitch v-if="advancedMfm" v-model="animatedMfm">{{ i18n.ts.enableAnimatedMfm }}</MkSwitch>
 				<MkSwitch v-model="reduceAnimation">{{ i18n.ts.reduceUiAnimation }}</MkSwitch>
 				<MkSwitch v-model="useBlurEffect">{{ i18n.ts.useBlurEffect }}</MkSwitch>
 				<MkSwitch v-model="useBlurEffectForModal">{{ i18n.ts.useBlurEffectForModal }}</MkSwitch>
-				<MkSwitch v-model="showGapBetweenNotesInTimeline">{{ i18n.ts.showGapBetweenNotesInTimeline }}</MkSwitch>
-				<MkSwitch v-model="loadRawImages">{{ i18n.ts.loadRawImages }}</MkSwitch>
 				<MkSwitch v-model="disableShowingAnimatedImages">{{ i18n.ts.disableShowingAnimatedImages }}</MkSwitch>
 				<MkSwitch v-model="squareAvatars">{{ i18n.ts.squareAvatars }}</MkSwitch>
 				<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
 				<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
 				<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
+				<MkSwitch v-model="enableDataSaverMode">{{ i18n.ts.dataSaver }}</MkSwitch>
 			</div>
 			<div>
 				<MkRadios v-model="emojiStyle">
@@ -84,27 +124,29 @@
 	</FormSection>
 
 	<FormSection>
-		<MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch>
-	</FormSection>
-
-	<MkSelect v-model="instanceTicker">
-		<template #label>{{ i18n.ts.instanceTicker }}</template>
-		<option value="none">{{ i18n.ts._instanceTicker.none }}</option>
-		<option value="remote">{{ i18n.ts._instanceTicker.remote }}</option>
-		<option value="always">{{ i18n.ts._instanceTicker.always }}</option>
-	</MkSelect>
+		<template #label>{{ i18n.ts.behavior }}</template>
 
-	<MkSelect v-model="nsfw">
-		<template #label>{{ i18n.ts.nsfw }}</template>
-		<option value="respect">{{ i18n.ts._nsfw.respect }}</option>
-		<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
-		<option value="force">{{ i18n.ts._nsfw.force }}</option>
-	</MkSelect>
+		<div class="_gaps_m">
+			<div class="_gaps_s">
+				<MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
+				<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
+			</div>
+			<MkSelect v-model="serverDisconnectedBehavior">
+				<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
+				<option value="reload">{{ i18n.ts._serverDisconnectedBehavior.reload }}</option>
+				<option value="dialog">{{ i18n.ts._serverDisconnectedBehavior.dialog }}</option>
+				<option value="quiet">{{ i18n.ts._serverDisconnectedBehavior.quiet }}</option>
+			</MkSelect>
+			<MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing>
+				<template #label>{{ i18n.ts.numberOfPageCache }}</template>
+				<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
+			</MkRange>
+		</div>
+	</FormSection>
 
-	<MkRange v-model="numberOfPageCache" :min="1" :max="10" :step="1" easing>
-		<template #label>{{ i18n.ts.numberOfPageCache }}</template>
-		<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
-	</MkRange>
+	<FormSection>
+		<MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch>
+	</FormSection>
 
 	<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
 
@@ -160,6 +202,7 @@ const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
 const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
 const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
 const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
+const enableDataSaverMode = computed(defaultStore.makeGetterSetter('enableDataSaverMode'));
 const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
 const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
 const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
@@ -170,6 +213,9 @@ const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfin
 const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
 const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
 const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
+const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
+const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
+const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
 
 watch(lang, () => {
 	miLocalStorage.setItem('lang', lang.value as string);
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index a8274f560139a0055b8a26e99d005a54848b5380..89b4104020f2d3b38585cb7f1006321ab44aae53 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -32,7 +32,7 @@
 					<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 				</div>
 			</MkFolder>
-			<MkFolder>
+			<MkFolder v-if="$i && !$i.movedTo">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
@@ -47,7 +47,7 @@
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder>
+			<MkFolder v-if="$i && !$i.movedTo">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
@@ -62,7 +62,7 @@
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder>
+			<MkFolder v-if="$i && !$i.movedTo">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
@@ -77,13 +77,28 @@
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 			</MkFolder>
-			<MkFolder>
+			<MkFolder v-if="$i && !$i.movedTo">
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
 			</MkFolder>
 		</div>
 	</FormSection>
+	<FormSection>
+		<template #label><i class="ti ti-antenna"></i> {{ i18n.ts.antennas }}</template>
+		<div class="_gaps_s">
+			<MkFolder>
+				<template #label>{{ i18n.ts.export }}</template>
+				<template #icon><i class="ti ti-download"></i></template>
+				<MkButton primary :class="$style.button" inline @click="exportAntennas()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
+			</MkFolder>
+			<MkFolder v-if="$i && !$i.movedTo">
+				<template #label>{{ i18n.ts.import }}</template>
+				<template #icon><i class="ti ti-upload"></i></template>
+				<MkButton primary :class="$style.button" inline @click="importAntennas($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
+			</MkFolder>
+		</div>
+	</FormSection>
 </div>
 </template>
 
@@ -97,6 +112,7 @@ import * as os from '@/os';
 import { selectFile } from '@/scripts/select-file';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { $i } from '@/account';
 
 const excludeMutingUsers = ref(false);
 const excludeInactiveUsers = ref(false);
@@ -150,6 +166,10 @@ const exportMuting = () => {
 	os.api('i/export-mute', {}).then(onExportSuccess).catch(onError);
 };
 
+const exportAntennas = () => {
+	os.api('i/export-antennas', {}).then(onExportSuccess).catch(onError);
+};
+
 const importFollowing = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
 	os.api('i/import-following', { fileId: file.id }).then(onImportSuccess).catch(onError);
@@ -170,6 +190,11 @@ const importBlocking = async (ev) => {
 	os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
 };
 
+const importAntennas = async (ev) => {
+	const file = await selectFile(ev.currentTarget ?? ev.target);
+	os.api('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
+};
+
 const headerActions = $computed(() => []);
 
 const headerTabs = $computed(() => []);
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 17af7417fda47e72548bc9f1a6d17267775e36ca..34a962ef4cf86849170c2a56ffa67065df8ba1f5 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -164,12 +164,12 @@ const menuDef = computed(() => [{
 		text: i18n.ts.importAndExport,
 		to: '/settings/import-export',
 		active: currentPage?.route.name === 'import-export',
-	}, /*{
+	}, {
 		icon: 'ti ti-plane',
-		text: i18n.ts.accountMigration,
+		text: `${i18n.ts.accountMigration} (${i18n.ts.experimental})`,
 		to: '/settings/migration',
 		active: currentPage?.route.name === 'migration',
-	},*/ {
+	}, {
 		icon: 'ti ti-dots',
 		text: i18n.ts.other,
 		to: '/settings/other',
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index 2ef8af7481d1b9b86c3fe51a3f4de2f7b07d6c7d..541992875e5bd009cb753d2fb415aa6f0ffb0af1 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -1,63 +1,121 @@
 <template>
 <div class="_gaps_m">
-	<FormSection first>
+	<FormInfo warn>
+		{{ i18n.ts.thisIsExperimentalFeature }}
+	</FormInfo>
+	<MkFolder :default-open="true">
+		<template #icon><i class="ti ti-plane-arrival"></i></template>
+		<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
+		<template #caption>{{ i18n.ts._accountMigration.moveFromSub }}</template>
+
+		<div class="_gaps_m">
+			<FormInfo>
+				{{ i18n.ts._accountMigration.moveFromDescription }}
+			</FormInfo>
+			<div>
+				<MkButton :disabled="accountAliases.length >= 10" inline style="margin-right: 8px;" @click="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+				<MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+			</div>
+			<div class="_gaps">
+				<MkInput v-for="(_, i) in accountAliases" v-model="accountAliases[i]">
+					<template #prefix><i class="ti ti-plane-arrival"></i></template>
+					<template #label>{{ i18n.t('_accountMigration.moveFromLabel', { n: i + 1 }) }}</template>
+				</MkInput>
+			</div>
+		</div>
+	</MkFolder>
+
+	<MkFolder :default-open="!!$i?.movedTo">
+		<template #icon><i class="ti ti-plane-departure"></i></template>
 		<template #label>{{ i18n.ts._accountMigration.moveTo }}</template>
-		<MkInput v-model="moveToAccount" manual-save>
-			<template #prefix><i class="ti ti-plane-departure"></i></template>
-			<template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template>
-		</MkInput>
-	</FormSection>
-	<FormInfo warn>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo>
 
-	<FormSection>
-		<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
-		<MkInput v-model="accountAlias" manual-save>
-			<template #prefix><i class="ti ti-plane-arrival"></i></template>
-			<template #label>{{ i18n.ts._accountMigration.moveFromLabel }}</template>
-		</MkInput>
-	</FormSection>
-	<FormInfo warn>{{ i18n.ts._accountMigration.moveFromDescription }}</FormInfo>
+		<div class="_gaps_m">
+			<FormInfo>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo>
+
+			<template v-if="$i && !$i.movedTo">
+				<FormInfo>{{ i18n.ts._accountMigration.moveAccountHowTo }}</FormInfo>
+				<FormInfo warn>{{ i18n.ts._accountMigration.moveCannotBeUndone }}</FormInfo>
+
+				<MkInput v-model="moveToAccount">
+					<template #prefix><i class="ti ti-plane-departure"></i></template>
+					<template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template>
+				</MkInput>
+				<MkButton inline danger :disabled="!moveToAccount" @click="move">
+					<i class="ti ti-check"></i> {{ i18n.ts._accountMigration.startMigration }}
+				</MkButton>
+			</template>
+			<template v-else-if="$i">
+				<FormInfo>{{ i18n.ts._accountMigration.postMigrationNote }}</FormInfo>
+				<FormInfo warn>{{ i18n.ts._accountMigration.movedAndCannotBeUndone }}</FormInfo>
+				<div>{{ i18n.ts._accountMigration.movedTo }}</div>
+				<MkUserInfo v-if="movedTo" :user="movedTo" class="_panel _shadow" />
+			</template>
+		</div>
+	</MkFolder>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from 'vue';
-import FormSection from '@/components/form/section.vue';
+import { ref } from 'vue';
 import FormInfo from '@/components/MkInfo.vue';
 import MkInput from '@/components/MkInput.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkUserInfo from '@/components/MkUserInfo.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { $i } from '@/account';
+import { toString } from 'misskey-js/built/acct';
+import { UserDetailed } from 'misskey-js/built/entities';
+import { unisonReload } from '@/scripts/unison-reload';
 
 const moveToAccount = ref('');
-const accountAlias = ref('');
+const movedTo = ref<UserDetailed>();
+const accountAliases = ref(['']);
+
+async function init() {
+	if ($i?.movedTo) {
+		movedTo.value = await os.api('users/show', { userId: $i.movedTo });
+	} else {
+		moveToAccount.value = '';
+	}
+
+	if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) {
+		const alsoKnownAs = await os.api('users/show', { userIds: $i.alsoKnownAs });
+		accountAliases.value = (alsoKnownAs && alsoKnownAs.length > 0) ? alsoKnownAs.map(user => `@${toString(user)}`) : [''];
+	} else {
+		accountAliases.value = [''];
+	}
+}
 
 async function move(): Promise<void> {
 	const account = moveToAccount.value;
 	const confirm = await os.confirm({
 		type: 'warning',
-		text: i18n.t('migrationConfirm', { account: account.toString() }),
+		text: i18n.t('_accountMigration.migrationConfirm', { account }),
 	});
 	if (confirm.canceled) return;
-	os.apiWithDialog('i/move', {
+	await os.apiWithDialog('i/move', {
 		moveToAccount: account,
 	});
+	unisonReload();
+}
+
+function add(): void {
+	accountAliases.value.push('');
 }
 
 async function save(): Promise<void> {
-	const account = accountAlias.value;
-	os.apiWithDialog('i/known-as', {
-		alsoKnownAs: account,
+	const alsoKnownAs = accountAliases.value.map(alias => alias.trim()).filter(alias => alias !== '');
+	const i = await os.apiWithDialog('i/update', {
+		alsoKnownAs,
 	});
+	$i.alsoKnownAs = i.alsoKnownAs;
+	init();
 }
 
-watch(accountAlias, async () => {
-	await save();
-});
-
-watch(moveToAccount, async () => {
-	await move();
-});
+init();
 
 definePageMetadata({
 	title: i18n.ts.accountMigration,
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 892ea61e75d02f4134e7912302159eae5394dea2..776305d723c087986b38d0f644384847e48e138d 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -1,32 +1,85 @@
 <template>
 <div class="_gaps_m">
+	<!--
 	<MkSwitch v-model="$i.injectFeaturedNote" @update:model-value="onChangeInjectFeaturedNote">
-		{{ i18n.ts.showFeaturedNotesInTimeline }}
+		<template #label>{{ i18n.ts.showFeaturedNotesInTimeline }}</template>
 	</MkSwitch>
+	-->
 
 	<!--
 	<MkSwitch v-model="reportError">{{ i18n.ts.sendErrorReports }}<template #caption>{{ i18n.ts.sendErrorReportsDescription }}</template></MkSwitch>
 	-->
 
-	<FormLink to="/settings/account-info">{{ i18n.ts.accountInfo }}</FormLink>
+	<FormSection first>
+		<div class="_gaps_s">
+			<MkFolder>
+				<template #icon><i class="ti ti-info-circle"></i></template>
+				<template #label>{{ i18n.ts.accountInfo }}</template>
+
+				<div class="_gaps_m">
+					<MkKeyValue>
+						<template #key>ID</template>
+						<template #value><span class="_monospace">{{ $i.id }}</span></template>
+					</MkKeyValue>
+
+					<MkKeyValue>
+						<template #key>{{ i18n.ts.registeredDate }}</template>
+						<template #value><MkTime :time="$i.createdAt" mode="detail"/></template>
+					</MkKeyValue>
+
+					<FormLink to="/settings/account-stats"><template #icon><i class="ti ti-info-circle"></i></template>{{ i18n.ts.statistics }}</FormLink>
+				</div>
+			</MkFolder>
+
+			<MkFolder>
+				<template #icon><i class="ti ti-alert-triangle"></i></template>
+				<template #label>{{ i18n.ts.closeAccount }}</template>
 
-	<FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink>
+				<div class="_gaps_m">
+					<FormInfo warn>{{ i18n.ts._accountDelete.mayTakeTime }}</FormInfo>
+					<FormInfo>{{ i18n.ts._accountDelete.sendEmail }}</FormInfo>
+					<MkButton v-if="!$i.isDeleted" danger @click="deleteAccount">{{ i18n.ts._accountDelete.requestAccountDelete }}</MkButton>
+					<MkButton v-else disabled>{{ i18n.ts._accountDelete.inProgress }}</MkButton>
+				</div>
+			</MkFolder>
 
-	<FormLink to="/settings/delete-account"><template #icon><i class="ti ti-alert-triangle"></i></template>{{ i18n.ts.closeAccount }}</FormLink>
+			<MkFolder>
+				<template #icon><i class="ti ti-flask"></i></template>
+				<template #label>{{ i18n.ts.experimentalFeatures }}</template>
+
+				<div class="_gaps_m">
+					<MkSwitch v-model="enableCondensedLineForAcct">
+						<template #label>Enable condensed line for acct</template>
+					</MkSwitch>
+				</div>
+			</MkFolder>
+		</div>
+	</FormSection>
+
+	<FormSection>
+		<FormLink to="/registry"><template #icon><i class="ti ti-adjustments"></i></template>{{ i18n.ts.registry }}</FormLink>
+	</FormSection>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, watch } from 'vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import FormLink from '@/components/form/link.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import FormInfo from '@/components/MkInfo.vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
-import { $i } from '@/account';
+import { signout, $i } from '@/account';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { unisonReload } from '@/scripts/unison-reload';
+import FormSection from '@/components/form/section.vue';
 
 const reportError = computed(defaultStore.makeGetterSetter('reportError'));
+const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct'));
 
 function onChangeInjectFeaturedNote(v) {
 	os.api('i/update', {
@@ -36,6 +89,48 @@ function onChangeInjectFeaturedNote(v) {
 	});
 }
 
+async function deleteAccount() {
+	{
+		const { canceled } = await os.confirm({
+			type: 'warning',
+			text: i18n.ts.deleteAccountConfirm,
+		});
+		if (canceled) return;
+	}
+
+	const { canceled, result: password } = await os.inputText({
+		title: i18n.ts.password,
+		type: 'password',
+	});
+	if (canceled) return;
+
+	await os.apiWithDialog('i/delete-account', {
+		password: password,
+	});
+
+	await os.alert({
+		title: i18n.ts._accountDelete.started,
+	});
+
+	await signout();
+}
+
+async function reloadAsk() {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
+
+	unisonReload();
+}
+
+watch([
+	enableCondensedLineForAcct,
+], async () => {
+	await reloadAsk();
+});
+
 const headerActions = $computed(() => []);
 
 const headerTabs = $computed(() => []);
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 092b9a9cc8eddb7a8a117f7b6d0c407c18f6062e..6613ce4c1d8639758668f1276e4d48c1c11c001c 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -88,6 +88,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'squareAvatars',
 	'numberOfPageCache',
 	'aiChanMode',
+	'mediaListWithOneImageAppearance',
 ];
 const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
 	'lightTheme',
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index a5f6c11f89d52819423f12f9d6ec595416b72320..6ffd6826108839dbe650f317d9f0f62c96ba0d2b 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -37,19 +37,40 @@
 			<template #icon><i class="ti ti-list"></i></template>
 			<template #label>{{ i18n.ts._profile.metadataEdit }}</template>
 
-			<div class="_gaps_m">
-				<FormSplit v-for="(record, i) in fields" :min-width="250">
-					<MkInput v-model="record.name" small>
-						<template #label>{{ i18n.ts._profile.metadataLabel }} #{{ i + 1 }}</template>
-					</MkInput>
-					<MkInput v-model="record.value" small>
-						<template #label>{{ i18n.ts._profile.metadataContent }} #{{ i + 1 }}</template>
-					</MkInput>
-				</FormSplit>
-				<div>
+			<div :class="$style.metadataRoot">
+				<div :class="$style.metadataMargin">
 					<MkButton :disabled="fields.length >= 16" inline style="margin-right: 8px;" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+					<MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" inline danger style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+					<MkButton v-else inline style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
 					<MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 				</div>
+
+				<Sortable
+					v-model="fields"
+					class="_gaps_s"
+					item-key="id"
+					:animation="150"
+					:handle="'.' + $style.dragItemHandle"
+					@start="e => e.item.classList.add('active')"
+					@end="e => e.item.classList.remove('active')"
+				>
+					<template #item="{element, index}">
+						<div :class="$style.fieldDragItem">
+							<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
+							<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
+							<div :class="$style.dragItemForm">
+								<FormSplit :min-width="200">
+									<MkInput v-model="element.name" small>
+										<template #label>{{ i18n.ts._profile.metadataLabel }}</template>
+									</MkInput>
+									<MkInput v-model="element.value" small>
+										<template #label>{{ i18n.ts._profile.metadataContent }}</template>
+									</MkInput>
+								</FormSplit>
+							</div>
+						</div>
+					</template>
+				</Sortable>
 			</div>
 		</MkFolder>
 		<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
@@ -76,7 +97,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, reactive, watch } from 'vue';
+import { computed, reactive, ref, watch, defineAsyncComponent, onMounted, onUnmounted } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -94,6 +115,8 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 import { claimAchievement } from '@/scripts/achievements';
 import { defaultStore } from '@/store';
 
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
+
 const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
 
 const profile = reactive({
@@ -113,22 +136,28 @@ watch(() => profile, () => {
 	deep: true,
 });
 
-const fields = reactive($i.fields.map(field => ({ name: field.name, value: field.value })));
+const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
+const fieldEditMode = ref(false);
 
 function addField() {
-	fields.push({
+	fields.value.push({
+		id: Math.random().toString(),
 		name: '',
 		value: '',
 	});
 }
 
-while (fields.length < 4) {
+while (fields.value.length < 4) {
 	addField();
 }
 
+function deleteField(index: number) { 
+	fields.value.splice(index, 1);
+}
+
 function saveFields() {
 	os.apiWithDialog('i/update', {
-		fields: fields.filter(field => field.name !== '' && field.value !== ''),
+		fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
 	});
 }
 
@@ -248,3 +277,60 @@ definePageMetadata({
 	}
 }
 </style>
+<style lang="scss" module>
+.metadataRoot {
+	container-type: inline-size;
+}
+
+.metadataMargin {
+	margin-bottom: 1.5em;
+}
+
+.fieldDragItem {
+	display: flex;
+	padding-bottom: .75em;
+	align-items: flex-end;
+	border-bottom: solid 0.5px var(--divider);
+
+	&:last-child {
+		border-bottom: 0;
+	}
+
+	/* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */
+	@container (max-width: 452px) {
+		align-items: center;
+	}
+}
+
+.dragItemHandle {
+	cursor: grab;
+	width: 32px;
+	height: 32px;
+	margin: 0 8px 0 0;
+	opacity: 0.5;
+	flex-shrink: 0;
+
+	&:active {
+		cursor: grabbing;
+	}
+}
+
+.dragItemRemove {
+	@extend .dragItemHandle;
+
+	color: #ff2a2a;
+	opacity: 1;
+	cursor: pointer;
+
+	&:hover, &:focus {
+		opacity: .7;
+	}
+	&:active {
+		cursor: pointer;
+	}
+}
+
+.dragItemForm {
+	flex-grow: 1;
+}
+</style>
diff --git a/packages/frontend/src/pages/timeline.tutorial.vue b/packages/frontend/src/pages/timeline.tutorial.vue
index 0d0c932a5c00e94220387c763bff4891964cc791..32228d28f41c808b4f5575fc83c615c4a5d039d5 100644
--- a/packages/frontend/src/pages/timeline.tutorial.vue
+++ b/packages/frontend/src/pages/timeline.tutorial.vue
@@ -1,7 +1,7 @@
 <template>
 <div :class="$style.container">
 	<div :class="$style.title">
-		<div :class="$style.titleText"><i class="ti ti-info-circle"></i> {{ i18n.ts._tutorial.title }}</div>
+		<div :class="$style.titleText"><i class="ti ti-info-circle"></i> {{ i18n.ts._timelineTutorial.title }}</div>
 		<div :class="$style.step">
 			<button class="_button" :class="$style.stepArrow" :disabled="tutorial === 0" @click="tutorial--">
 				<i class="ti ti-chevron-left"></i>
@@ -12,66 +12,30 @@
 			</button>
 		</div>
 	</div>
+
 	<div v-if="tutorial === 0" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step1_1 }}</div>
-		<div>{{ i18n.ts._tutorial.step1_2 }}</div>
-		<div>{{ i18n.ts._tutorial.step1_3 }}</div>
+		<div>{{ i18n.t('_timelineTutorial.step1_1', { name: instance.name ?? host }) }}</div>
+		<div>{{ i18n.t('_timelineTutorial.step1_2', { name: instance.name ?? host }) }}</div>
 	</div>
 	<div v-else-if="tutorial === 1" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step2_1 }}</div>
-		<div>{{ i18n.ts._tutorial.step2_2 }}</div>
-		<MkA class="_link" to="/settings/profile">{{ i18n.ts.editProfile }}</MkA>
+		<div>{{ i18n.ts._timelineTutorial.step2_1 }}</div>
+		<div>{{ i18n.t('_timelineTutorial.step2_2', { name: instance.name ?? host }) }}</div>
 	</div>
 	<div v-else-if="tutorial === 2" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step3_1 }}</div>
-		<div>{{ i18n.ts._tutorial.step3_2 }}</div>
-		<div>{{ i18n.ts._tutorial.step3_3 }}</div>
-		<small :class="$style.small">{{ i18n.ts._tutorial.step3_4 }}</small>
+		<div>{{ i18n.ts._timelineTutorial.step3_1 }}</div>
+		<div>{{ i18n.ts._timelineTutorial.step3_2 }}</div>
 	</div>
 	<div v-else-if="tutorial === 3" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step4_1 }}</div>
-		<div>{{ i18n.ts._tutorial.step4_2 }}</div>
-	</div>
-	<div v-else-if="tutorial === 4" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step5_1 }}</div>
-		<I18n :src="i18n.ts._tutorial.step5_2" tag="div">
-			<template #featured>
-				<MkA class="_link" to="/explore">{{ i18n.ts.featured }}</MkA>
-			</template>
-			<template #explore>
-				<MkA class="_link" to="/explore#users">{{ i18n.ts.explore }}</MkA>
-			</template>
-		</I18n>
-		<div>{{ i18n.ts._tutorial.step5_3 }}</div>
-		<small :class="$style.small">{{ i18n.ts._tutorial.step5_4 }}</small>
-	</div>
-	<div v-else-if="tutorial === 5" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step6_1 }}</div>
-		<div>{{ i18n.ts._tutorial.step6_2 }}</div>
-		<div>{{ i18n.ts._tutorial.step6_3 }}</div>
-	</div>
-	<div v-else-if="tutorial === 6" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step7_1 }}</div>
-		<I18n :src="i18n.ts._tutorial.step7_2" tag="div">
-			<template #help>
-				<a href="https://misskey-hub.net/help.html" target="_blank" class="_link">{{ i18n.ts.help }}</a>
-			</template>
-		</I18n>
-		<div>{{ i18n.ts._tutorial.step7_3 }}</div>
-	</div>
-	<div v-else-if="tutorial === 7" :class="$style.body">
-		<div>{{ i18n.ts._tutorial.step8_1 }}</div>
-		<div>{{ i18n.ts._tutorial.step8_2 }}</div>
-		<small :class="$style.small">{{ i18n.ts._tutorial.step8_3 }}</small>
+		<div>{{ i18n.ts._timelineTutorial.step4_1 }}</div>
+		<div>{{ i18n.ts._timelineTutorial.step4_2 }}</div>
 	</div>
 
 	<div :class="$style.footer">
 		<template v-if="tutorial === tutorialsNumber - 1">
-			<MkPushNotificationAllowButton :class="$style.footerItem" primary show-only-to-register @click="tutorial = -1"/>
-			<MkButton :class="$style.footerItem" :primary="false" @click="tutorial = -1">{{ i18n.ts.noThankYou }}</MkButton>
+			<MkButton :class="$style.footerItem" primary rounded gradate @click="tutorial = -1">{{ i18n.ts.done }} <i class="ti ti-check"></i></MkButton>
 		</template>
 		<template v-else>
-			<MkButton :class="$style.footerItem" primary @click="tutorial++"><i class="ti ti-check"></i> {{ i18n.ts.next }}</MkButton>
+			<MkButton :class="$style.footerItem" primary rounded gradate @click="tutorial++">{{ i18n.ts.next }} <i class="ti ti-arrow-right"></i></MkButton>
 		</template>
 	</div>
 </div>
@@ -80,15 +44,16 @@
 <script lang="ts" setup>
 import { computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
-import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+import { host } from '@/config';
 
-const tutorialsNumber = 8;
+const tutorialsNumber = 4;
 
 const tutorial = computed({
-	get() { return defaultStore.reactiveState.tutorial.value || 0; },
-	set(value) { defaultStore.set('tutorial', value); },
+	get() { return defaultStore.reactiveState.timelineTutorial.value || 0; },
+	set(value) { defaultStore.set('timelineTutorial', value); },
 });
 </script>
 
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 9f13f7a1dd33c717fa6835e5d299912a21d5f985..1bf4cdc99ae4ee1ae6222fff1b65133a3c8f5b48 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -3,7 +3,7 @@
 	<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :display-my-avatar="true"/></template>
 	<MkSpacer :content-max="800">
 		<div ref="rootEl" v-hotkey.global="keymap">
-			<XTutorial v-if="$i && defaultStore.reactiveState.tutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
+			<XTutorial v-if="$i && defaultStore.reactiveState.timelineTutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
 			<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
 
 			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 8c3478d8f2260fc36248fb482ae2259cfba41365..5bc157826891eb6705f7992d3f12bda4fb3ef263 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -7,7 +7,7 @@
 			<!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
 
 			<div class="profile _gaps">
-				<MkAccountMoved v-if="user.movedToUri" :host="user.movedToUri.host" :acct="user.movedToUri.username"/>
+				<MkAccountMoved v-if="user.movedTo" :moved-to="user.movedTo"/>
 				<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
 
 				<div :key="user.id" class="main _panel">
@@ -21,6 +21,9 @@
 								<span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span>
 								<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span>
 								<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span>
+								<button v-if="!isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
+									<i class="ti ti-edit"/> {{ i18n.ts.addMemo }}
+								</button>
 							</div>
 						</div>
 						<span v-if="$i && $i.id != user.id && user.isFollowed" class="followed">{{ i18n.ts.followsYou }}</span>
@@ -45,6 +48,22 @@
 							{{ role.name }}
 						</span>
 					</div>
+					<div v-if="iAmModerator" class="moderationNote">
+						<MkTextarea v-model="moderationNote" manual-save>
+							<template #label>Moderation note</template>
+						</MkTextarea>
+					</div>
+					<div v-if="isEditingMemo || memoDraft" class="memo" :class="{'no-memo': !memoDraft}">
+						<div class="heading" v-text="i18n.ts.memo"/>
+						<textarea
+							ref="memoTextareaEl"
+							v-model="memoDraft"
+							rows="1"
+							@focus="isEditingMemo = true"
+							@blur="updateMemo"
+							@input="adjustMemoTextarea"
+						/>
+					</div>
 					<div class="description">
 						<MkOmit>
 							<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
@@ -113,13 +132,14 @@
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, computed, onMounted, onUnmounted } from 'vue';
+import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch } from 'vue';
 import calcAge from 's-age';
 import * as misskey from 'misskey-js';
 import MkNote from '@/components/MkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import MkAccountMoved from '@/components/MkAccountMoved.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
+import MkTextarea from '@/components/MkTextarea.vue';
 import MkOmit from '@/components/MkOmit.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { getScrollPosition } from '@/scripts/scroll';
@@ -129,10 +149,11 @@ import { userPage } from '@/filters/user';
 import * as os from '@/os';
 import { useRouter } from '@/router';
 import { i18n } from '@/i18n';
-import { $i } from '@/account';
+import { $i, iAmModerator } from '@/account';
 import { dateString } from '@/filters/date';
 import { confetti } from '@/scripts/confetti';
 import MkNotes from '@/components/MkNotes.vue';
+import { api } from '@/os';
 
 const XPhotos = defineAsyncComponent(() => import('./index.photos.vue'));
 const XActivity = defineAsyncComponent(() => import('./index.activity.vue'));
@@ -151,6 +172,14 @@ let parallaxAnimationId = $ref<null | number>(null);
 let narrow = $ref<null | boolean>(null);
 let rootEl = $ref<null | HTMLElement>(null);
 let bannerEl = $ref<null | HTMLElement>(null);
+let memoTextareaEl = $ref<null | HTMLElement>(null);
+let memoDraft = $ref(props.user.memo);
+let isEditingMemo = $ref(false);
+let moderationNote = $ref(props.user.moderationNote);
+
+watch($$(moderationNote), async () => {
+	await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote });
+});
 
 const pagination = {
 	endpoint: 'users/notes' as const,
@@ -193,6 +222,31 @@ function parallax() {
 	banner.style.backgroundPosition = `center calc(50% - ${pos}px)`;
 }
 
+function showMemoTextarea() {
+	isEditingMemo = true;
+	nextTick(() => {
+		memoTextareaEl?.focus();
+	});
+}
+
+function adjustMemoTextarea() {
+	if (!memoTextareaEl) return;
+	memoTextareaEl.style.height = '0px';
+	memoTextareaEl.style.height = `${memoTextareaEl.scrollHeight}px`;
+}
+
+async function updateMemo() {
+	await api('users/update-memo', {
+		memo: memoDraft,
+		userId: props.user.id,
+	});
+	isEditingMemo = false;
+}
+
+watch([props.user], () => {
+	memoDraft = props.user.memo;
+});
+
 onMounted(() => {
 	window.requestAnimationFrame(parallaxLoop);
 	narrow = rootEl!.clientWidth < 1000;
@@ -208,6 +262,9 @@ onMounted(() => {
 			});
 		}
 	}
+	nextTick(() => {
+		adjustMemoTextarea();
+	});
 });
 
 onUnmounted(() => {
@@ -323,6 +380,16 @@ onUnmounted(() => {
 									font-weight: bold;
 								}
 							}
+
+							> .add-note-button {
+								background: rgba(0, 0, 0, 0.2);
+								color: #fff;
+								-webkit-backdrop-filter: var(--blur, blur(8px));
+								backdrop-filter: var(--blur, blur(8px));
+								border-radius: 24px;
+								padding: 4px 8px;
+								font-size: 80%;
+							}
 						}
 					}
 				}
@@ -369,6 +436,43 @@ onUnmounted(() => {
 					}
 				}
 
+				> .moderationNote {
+					margin: 12px 24px 0 154px;
+				}
+
+				> .memo {
+					margin: 12px 24px 0 154px;
+					background: transparent;
+					color: var(--fg);
+					border: 1px solid var(--divider);
+					border-radius: 8px;
+					padding: 8px;
+					line-height: 0;
+
+					> .heading {
+						text-align: left;
+						color: var(--fgTransparent);
+						line-height: 1.5;
+						font-size: 85%;
+					}
+
+					textarea {
+						margin: 0;
+						padding: 0;
+						resize: none;
+						border: none;
+						outline: none;
+						width: 100%;
+						height: auto;
+						min-height: 0;
+						line-height: 1.5;
+						color: var(--fg);
+						overflow: hidden;
+						background: transparent;
+						font-family: inherit;
+					}
+				}
+
 				> .description {
 					padding: 24px 24px 24px 154px;
 					font-size: 0.95em;
@@ -504,6 +608,14 @@ onUnmounted(() => {
 					justify-content: center;
 				}
 
+				> .moderationNote {
+					margin: 16px 16px 0 16px;
+				}
+
+				> .memo {
+					margin: 16px 16px 0 16px;
+				}
+
 				> .description {
 					padding: 16px;
 					text-align: center;
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index 4d8d76db18f30e0c6899cb14759792c83642d649..929152bd5abcf86f8e0f960c03f6003864b96ea4 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -13,35 +13,7 @@
 		<MkEmoji :normal="true" :no-style="true" emoji="🍮"/>
 	</div>
 	<div class="contents">
-		<div class="main">
-			<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
-			<button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button>
-			<div class="fg">
-				<h1>
-					<!-- 背景色によってはロゴが見えなくなるのでとりあえず無効に -->
-					<!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> -->
-					<span class="text">{{ instanceName }}</span>
-				</h1>
-				<div class="about">
-					<!-- eslint-disable-next-line vue/no-v-html -->
-					<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
-				</div>
-				<div v-if="instance.disableRegistration" class="warn">
-					<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
-				</div>
-				<div class="action _gaps_s">
-					<MkButton full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
-					<MkButton full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton>
-					<MkButton full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
-				</div>
-			</div>
-		</div>
-		<div v-if="instance.policies.ltlAvailable" class="tl">
-			<div class="title">{{ i18n.ts.letsLookAtTimeline }}</div>
-			<div class="body">
-				<MkTimeline src="local"/>
-			</div>
-		</div>
+		<MkVisitorDashboard/>
 	</div>
 	<div v-if="instances && instances.length > 0" class="federation">
 		<MarqueeText :duration="40">
@@ -60,16 +32,15 @@ import { } from 'vue';
 import { Instance } from 'misskey-js/built/entities';
 import XTimeline from './welcome.timeline.vue';
 import MarqueeText from '@/components/MkMarquee.vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/MkButton.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
-import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { instanceName } from '@/config';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
+import number from '@/filters/number';
+import MkNumber from '@/components/MkNumber.vue';
+import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 
 let meta = $ref<Instance>();
 let instances = $ref<any[]>();
@@ -84,45 +55,6 @@ os.apiGet('federation/instances', {
 }).then(_instances => {
 	instances = _instances;
 });
-
-function signin() {
-	os.popup(XSigninDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-function signup() {
-	os.popup(XSignupDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-function showMenu(ev) {
-	os.popupMenu([{
-		text: i18n.ts.instanceInfo,
-		icon: 'ti ti-info-circle',
-		action: () => {
-			os.pageWindow('/about');
-		},
-	}, {
-		text: i18n.ts.aboutMisskey,
-		icon: 'ti ti-info-circle',
-		action: () => {
-			os.pageWindow('/about-misskey');
-		},
-	}, null, {
-		text: i18n.ts.help,
-		icon: 'ti ti-question-circle',
-		action: () => {
-			window.open('https://misskey-hub.net/help.md', '_blank');
-		},
-	}], ev.currentTarget ?? ev.target);
-}
-
-function exploreOtherServers() {
-	// TODO: 言語をよしなに
-	window.open('https://join.misskey.page/ja-JP/instances', '_blank');
-}
 </script>
 
 <style lang="scss" scoped>
@@ -202,89 +134,11 @@ function exploreOtherServers() {
 		position: relative;
 		width: min(430px, calc(100% - 32px));
 		margin-left: 128px;
-		padding: 150px 0 100px 0;
+		padding: 100px 0 100px 0;
 
 		@media (max-width: 1200px) {
 			margin: auto;
 		}
-
-		> .main {
-			position: relative;
-			background: var(--panel);
-			border-radius: var(--radius);
-			box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-			text-align: center;
-		
-			> .icon {
-				width: 85px;
-				margin-top: -47px;
-				border-radius: 100%;
-				vertical-align: bottom;
-			}
-
-			> .menu {
-				position: absolute;
-				top: 16px;
-				right: 16px;
-				width: 32px;
-				height: 32px;
-				border-radius: 8px;
-				font-size: 18px;
-			}
-
-			> .fg {
-				position: relative;
-				z-index: 1;
-
-				> h1 {
-					display: block;
-					margin: 0;
-					padding: 16px 32px 24px 32px;
-					font-size: 1.4em;
-
-					> .logo {
-						vertical-align: bottom;
-						max-height: 120px;
-						max-width: min(100%, 300px);
-					}
-				}
-
-				> .about {
-					padding: 0 32px;
-				}
-
-				> .warn {
-					padding: 32px 32px 0 32px;
-				}
-
-				> .action {
-					padding: 32px;
-
-					> * {
-						line-height: 28px;
-					}
-				}
-			}
-		}
-
-		> .tl {
-			position: relative;
-			background: var(--panel);
-			border-radius: var(--radius);
-			overflow: clip;
-			box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-			margin-top: 16px;
-
-			> .title {
-				padding: 12px 16px;
-				border-bottom: solid 1px var(--divider);
-			}
-
-			> .body {
-				height: 350px;
-				overflow: auto;
-			}
-		}
 	}
 
 	> .federation {
diff --git a/packages/frontend/src/pages/welcome.entrance.b.vue b/packages/frontend/src/pages/welcome.entrance.b.vue
deleted file mode 100644
index 03bf174710005a0ed7e2b8a2ccbf899eabc2927f..0000000000000000000000000000000000000000
--- a/packages/frontend/src/pages/welcome.entrance.b.vue
+++ /dev/null
@@ -1,239 +0,0 @@
-<template>
-<div v-if="meta" class="rsqzvsbo">
-	<div class="top">
-		<MkFeaturedPhotos class="bg"/>
-		<XTimeline class="tl"/>
-		<div class="shape"></div>
-		<div class="main">
-			<h1>
-				<img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span>
-			</h1>
-			<div class="about">
-				<!-- eslint-disable-next-line vue/no-v-html -->
-				<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
-			</div>
-			<div class="action">
-				<MkButton class="signup" inline gradate @click="signup()">{{ i18n.ts.signup }}</MkButton>
-				<MkButton class="signin" inline @click="signin()">{{ i18n.ts.login }}</MkButton>
-			</div>
-			<div v-if="onlineUsersCount && stats" class="status">
-				<div>
-					<I18n :src="i18n.ts.nUsers" text-tag="span" class="users">
-						<template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
-					</I18n>
-					<I18n :src="i18n.ts.nNotes" text-tag="span" class="notes">
-						<template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
-					</I18n>
-				</div>
-				<I18n :src="i18n.ts.onlineUsersCount" text-tag="span" class="online">
-					<template #n><b>{{ onlineUsersCount }}</b></template>
-				</I18n>
-			</div>
-		</div>
-		<img src="/client-assets/misskey.svg" class="misskey"/>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { toUnicode } from 'punycode/';
-import XTimeline from './welcome.timeline.vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkNote from '@/components/MkNote.vue';
-import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import number from '@/filters/number';
-import { i18n } from '@/i18n';
-
-export default defineComponent({
-	components: {
-		MkButton,
-		MkNote,
-		XTimeline,
-		MkFeaturedPhotos,
-	},
-
-	data() {
-		return {
-			host: toUnicode(host),
-			instanceName,
-			meta: null,
-			stats: null,
-			tags: [],
-			onlineUsersCount: null,
-			i18n,
-		};
-	},
-
-	created() {
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-
-		os.api('stats').then(stats => {
-			this.stats = stats;
-		});
-
-		os.api('get-online-users-count').then(res => {
-			this.onlineUsersCount = res.count;
-		});
-
-		os.api('hashtags/list', {
-			sort: '+mentionedLocalUsers',
-			limit: 8,
-		}).then(tags => {
-			this.tags = tags;
-		});
-	},
-
-	methods: {
-		signin() {
-			os.popup(XSigninDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		signup() {
-			os.popup(XSignupDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		showMenu(ev) {
-			os.popupMenu([{
-				text: i18n.t('aboutX', { x: instanceName }),
-				icon: 'ti ti-info-circle',
-				action: () => {
-					os.pageWindow('/about');
-				},
-			}, {
-				text: i18n.ts.aboutMisskey,
-				icon: 'ti ti-info-circle',
-				action: () => {
-					os.pageWindow('/about-misskey');
-				},
-			}, null, {
-				text: i18n.ts.help,
-				icon: 'ti ti-question-circle',
-				action: () => {
-					window.open('https://misskey-hub.net/help.md', '_blank');
-				},
-			}], ev.currentTarget ?? ev.target);
-		},
-
-		number,
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.rsqzvsbo {
-	> .top {
-		min-height: 100vh;
-		box-sizing: border-box;
-
-		> .bg {
-			position: absolute;
-			top: 0;
-			left: 0;
-			width: 100%;
-			height: 100%;
-		}
-
-		> .tl {
-			position: absolute;
-			top: 0;
-			bottom: 0;
-			right: 64px;
-			margin: auto;
-			width: 500px;
-			height: calc(100% - 128px);
-			overflow: hidden;
-			-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
-			mask-image: linear-gradient(0deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 128px, rgba(0,0,0,1) calc(100% - 128px), rgba(0,0,0,0) 100%);
-		}
-
-		> .shape {
-			position: absolute;
-			top: 0;
-			left: 0;
-			width: 100%;
-			height: 100%;
-			background: var(--accent);
-			clip-path: polygon(0% 0%, 40% 0%, 22% 100%, 0% 100%);
-		}
-
-		> .misskey {
-			position: absolute;
-			bottom: 64px;
-			left: 64px;
-			width: 160px;
-		}
-
-		> .main {
-			position: relative;
-			width: min(450px, 100%);
-			padding: 64px;
-			color: #fff;
-			font-size: 1.1em;
-
-			@media (max-width: 1200px) {
-				margin: auto;
-			}
-
-			> h1 {
-				display: block;
-				margin: 0 0 32px 0;
-				padding: 0;
-
-				> .logo {
-					vertical-align: bottom;
-					max-height: 100px;
-				}
-			}
-
-			> .about {
-				padding: 0;
-			}
-
-			> .action {
-				margin: 32px 0;
-
-				> * {
-					line-height: 32px;
-				}
-
-				> .signup {
-					background: var(--panel);
-					color: var(--fg);
-				}
-
-				> .signin {
-					background: var(--accent);
-					color: inherit;
-				}
-			}
-
-			> .status {
-				margin: 32px 0;
-				border-top: solid 1px rgba(255, 255, 255, 0.5);
-				font-size: 90%;
-
-				> div {
-					padding: 16px 0;
-
-					> span:not(:last-child) {
-						padding-right: 1em;
-						margin-right: 1em;
-						border-right: solid 1px rgba(255, 255, 255, 0.5);
-					}
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/pages/welcome.entrance.c.vue b/packages/frontend/src/pages/welcome.entrance.c.vue
deleted file mode 100644
index eca4e5764dd5e8d7aca956c4ff7b839822c56472..0000000000000000000000000000000000000000
--- a/packages/frontend/src/pages/welcome.entrance.c.vue
+++ /dev/null
@@ -1,308 +0,0 @@
-<template>
-<div v-if="meta" class="rsqzvsbo">
-	<div class="top">
-		<MkFeaturedPhotos class="bg"/>
-		<div class="fade"></div>
-		<div class="emojis">
-			<MkEmoji :normal="true" :no-style="true" emoji="👍"/>
-			<MkEmoji :normal="true" :no-style="true" emoji="❤"/>
-			<MkEmoji :normal="true" :no-style="true" emoji="😆"/>
-			<MkEmoji :normal="true" :no-style="true" emoji="🎉"/>
-			<MkEmoji :normal="true" :no-style="true" emoji="🍮"/>
-		</div>
-		<div class="main">
-			<img src="/client-assets/misskey.svg" class="misskey"/>
-			<div class="form _panel">
-				<div class="bg">
-					<div class="fade"></div>
-				</div>
-				<div class="fg">
-					<h1>
-						<img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span>
-					</h1>
-					<div class="about">
-						<!-- eslint-disable-next-line vue/no-v-html -->
-						<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
-					</div>
-					<div class="action">
-						<MkButton inline gradate @click="signup()">{{ i18n.ts.signup }}</MkButton>
-						<MkButton inline @click="signin()">{{ i18n.ts.login }}</MkButton>
-					</div>
-					<div v-if="onlineUsersCount && stats" class="status">
-						<div>
-							<I18n :src="i18n.ts.nUsers" text-tag="span" class="users">
-								<template #n><b>{{ number(stats.originalUsersCount) }}</b></template>
-							</I18n>
-							<I18n :src="i18n.ts.nNotes" text-tag="span" class="notes">
-								<template #n><b>{{ number(stats.originalNotesCount) }}</b></template>
-							</I18n>
-						</div>
-						<I18n :src="i18n.ts.onlineUsersCount" text-tag="span" class="online">
-							<template #n><b>{{ onlineUsersCount }}</b></template>
-						</I18n>
-					</div>
-					<button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button>
-				</div>
-			</div>
-			<nav class="nav">
-				<MkA to="/announcements">{{ i18n.ts.announcements }}</MkA>
-				<MkA to="/explore">{{ i18n.ts.explore }}</MkA>
-				<MkA to="/channels">{{ i18n.ts.channel }}</MkA>
-				<MkA to="/featured">{{ i18n.ts.featured }}</MkA>
-			</nav>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { toUnicode } from 'punycode/';
-import XTimeline from './welcome.timeline.vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkNote from '@/components/MkNote.vue';
-import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import number from '@/filters/number';
-import { i18n } from '@/i18n';
-
-export default defineComponent({
-	components: {
-		MkButton,
-		MkNote,
-		MkFeaturedPhotos,
-		XTimeline,
-	},
-
-	data() {
-		return {
-			host: toUnicode(host),
-			instanceName,
-			meta: null,
-			stats: null,
-			tags: [],
-			onlineUsersCount: null,
-			i18n,
-		};
-	},
-
-	created() {
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-
-		os.api('stats').then(stats => {
-			this.stats = stats;
-		});
-
-		os.api('get-online-users-count').then(res => {
-			this.onlineUsersCount = res.count;
-		});
-
-		os.api('hashtags/list', {
-			sort: '+mentionedLocalUsers',
-			limit: 8,
-		}).then(tags => {
-			this.tags = tags;
-		});
-	},
-
-	methods: {
-		signin() {
-			os.popup(XSigninDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		signup() {
-			os.popup(XSignupDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		showMenu(ev) {
-			os.popupMenu([{
-				text: i18n.t('aboutX', { x: instanceName }),
-				icon: 'ti ti-info-circle',
-				action: () => {
-					os.pageWindow('/about');
-				},
-			}, {
-				text: i18n.ts.aboutMisskey,
-				icon: 'ti ti-info-circle',
-				action: () => {
-					os.pageWindow('/about-misskey');
-				},
-			}, null, {
-				text: i18n.ts.help,
-				icon: 'ti ti-question-circle',
-				action: () => {
-					window.open('https://misskey-hub.net/help.md', '_blank');
-				},
-			}], ev.currentTarget ?? ev.target);
-		},
-
-		number,
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.rsqzvsbo {
-	> .top {
-		display: flex;
-		text-align: center;
-		min-height: 100vh;
-		box-sizing: border-box;
-		padding: 16px;
-
-		> .bg {
-			position: absolute;
-			top: 0;
-			left: 0;
-			width: 100%;
-			height: 100%;
-		}
-
-		> .fade {
-			position: absolute;
-			top: 0;
-			left: 0;
-			width: 100%;
-			height: 100%;
-			background: rgba(0, 0, 0, 0.25);
-		}
-
-		> .emojis {
-			position: absolute;
-			bottom: 32px;
-			left: 35px;
-
-			> * {
-				margin-right: 8px;
-			}
-
-			@media (max-width: 1200px) {
-				display: none;
-			}
-		}
-
-		> .main {
-			position: relative;
-			width: min(460px, 100%);
-			margin: auto;
-
-			> .misskey {
-				width: 150px;
-				margin-bottom: 16px;
-
-				@media (max-width: 450px) {
-					width: 130px;
-				}
-			}
-
-			> .form {
-				position: relative;
-				box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-
-				> .bg {
-					position: absolute;
-					top: 0;
-					left: 0;
-					width: 100%;
-					height: 128px;
-					background-position: center;
-					background-size: cover;
-					opacity: 0.75;
-
-					> .fade {
-						position: absolute;
-						bottom: 0;
-						left: 0;
-						width: 100%;
-						height: 128px;
-						background: linear-gradient(0deg, var(--panel), var(--X15));
-					}
-				}
-
-				> .fg {
-					position: relative;
-					z-index: 1;
-
-					> h1 {
-						display: block;
-						margin: 0;
-						padding: 32px 32px 24px 32px;
-
-						> .logo {
-							vertical-align: bottom;
-							max-height: 120px;
-						}
-					}
-
-					> .about {
-						padding: 0 32px;
-					}
-
-					> .action {
-						padding: 32px;
-
-						> * {
-							line-height: 28px;
-						}
-					}
-
-					> .status {
-						border-top: solid 0.5px var(--divider);
-						padding: 32px;
-						font-size: 90%;
-
-						> div {
-							> span:not(:last-child) {
-								padding-right: 1em;
-								margin-right: 1em;
-								border-right: solid 0.5px var(--divider);
-							}
-						}
-
-						> .online {
-							::v-deep(b) {
-								color: #41b781;
-							}
-
-							::v-deep(span) {
-								opacity: 0.7;
-							}
-						}
-					}
-
-					> .menu {
-						position: absolute;
-						top: 16px;
-						right: 16px;
-						width: 32px;
-						height: 32px;
-						border-radius: 8px;
-					}
-				}
-			}
-
-			> .nav {
-				position: relative;
-				z-index: 2;
-				margin-top: 20px;
-				color: #fff;
-				text-shadow: 0 0 8px black;
-				font-size: 0.9em;
-
-				> *:not(:last-child) {
-					margin-right: 1.5em;
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 212d156a8349adf5a5eff611c82ae72d2c25f10c..7728d97a6554f348bf763994b521ceaeb0d429a6 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -1,8 +1,11 @@
 <template>
-<form class="mk-setup" @submit.prevent="submit()">
-	<h1>Welcome to Misskey!</h1>
-	<div class="_gaps_m">
-		<p>{{ i18n.ts.intro }}</p>
+<form :class="$style.root" class="_panel" @submit.prevent="submit()">
+	<div :class="$style.title">
+		<div>Welcome to Misskey!</div>
+		<div :class="$style.version">v{{ version }}</div>
+	</div>
+	<div class="_gaps_m" style="padding: 32px;">
+		<div>{{ i18n.ts.intro }}</div>
 		<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
 			<template #label>{{ i18n.ts.username }}</template>
 			<template #prefix>@</template>
@@ -12,8 +15,8 @@
 			<template #label>{{ i18n.ts.password }}</template>
 			<template #prefix><i class="ti ti-lock"></i></template>
 		</MkInput>
-		<div class="bottom">
-			<MkButton gradate type="submit" :disabled="submitting" data-cy-admin-ok>
+		<div>
+			<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
 				{{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/>
 			</MkButton>
 		</div>
@@ -25,7 +28,7 @@
 import { } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
-import { host } from '@/config';
+import { host, version } from '@/config';
 import * as os from '@/os';
 import { login } from '@/account';
 import { i18n } from '@/i18n';
@@ -54,36 +57,28 @@ function submit() {
 }
 </script>
 
-<style lang="scss" scoped>
-.mk-setup {
+<style lang="scss" module>
+.root {
 	border-radius: var(--radius);
 	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
 	overflow: hidden;
 	max-width: 500px;
 	margin: 32px auto;
+}
 
-	> h1 {
-		margin: 0;
-		font-size: 1.5em;
-		text-align: center;
-		padding: 32px;
-		background: var(--accent);
-		color: #fff;
-	}
-
-	> div {
-		padding: 32px;
-		background: var(--panel);
-
-		> p {
-			margin-top: 0;
-		}
+.title {
+	margin: 0;
+	font-size: 1.5em;
+	text-align: center;
+	padding: 32px;
+	background: var(--accentedBg);
+	color: var(--accent);
+	font-weight: bold;
+}
 
-		> .bottom {
-			> * {
-				margin: 0 auto;
-			}
-		}
-	}
+.version {
+	font-size: 70%;
+	font-weight: normal;
+	opacity: 0.7;
 }
 </style>
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 6a507ee1ed44eb78c658a6fbedd18c9ffd1c2600..6ec6e3f8633ef977c137b7fcb0c5ecb35aec95bd 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -33,7 +33,7 @@ import { $i } from '@/account';
 
 let notes = $ref<Note[]>([]);
 let isScrolling = $ref(false);
-let scrollEl = $ref<HTMLElement>();
+let scrollEl = $shallowRef<HTMLElement>();
 
 os.apiGet('notes/featured').then(_notes => {
 	notes = _notes;
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 0769ec2614b59833d087ea2090f46dea6b9ae1d2..e46c1eeb77ebd70fdedc64cc68a463cfbfb6df22 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -164,7 +164,7 @@ export const routes = [{
 	}, {
 		path: '/migration',
 		name: 'migration',
-		component: page(() => import('./pages/settings/migration.vue'))
+		component: page(() => import('./pages/settings/migration.vue')),
 	}, {
 		path: '/custom-css',
 		name: 'general',
@@ -174,13 +174,9 @@ export const routes = [{
 		name: 'profile',
 		component: page(() => import('./pages/settings/accounts.vue')),
 	}, {
-		path: '/account-info',
+		path: '/account-stats',
 		name: 'other',
-		component: page(() => import('./pages/settings/account-info.vue')),
-	}, {
-		path: '/delete-account',
-		name: 'other',
-		component: page(() => import('./pages/settings/delete-account.vue')),
+		component: page(() => import('./pages/settings/account-stats.vue')),
 	}, {
 		path: '/other',
 		name: 'other',
@@ -427,6 +423,10 @@ export const routes = [{
 		path: '/other-settings',
 		name: 'other-settings',
 		component: page(() => import('./pages/admin/other-settings.vue')),
+	}, {
+		path: '/server-rules',
+		name: 'server-rules',
+		component: page(() => import('./pages/admin/server-rules.vue')),
 	}, {
 		path: '/',
 		component: page(() => import('./pages/_empty_.vue')),
diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/scripts/achievements.ts
index 25e8b71a127827bc19abf17ae69ebd1feb76867a..fbca0057690d9e9d3e194dd8143dca18cf286fc3 100644
--- a/packages/frontend/src/scripts/achievements.ts
+++ b/packages/frontend/src/scripts/achievements.ts
@@ -60,6 +60,7 @@ export const ACHIEVEMENT_TYPES = [
 	'iLoveMisskey',
 	'foundTreasure',
 	'client30min',
+	'client60min',
 	'noteDeletedWithin1min',
 	'postedAtLateNight',
 	'postedAt0min0sec',
@@ -343,6 +344,11 @@ export const ACHIEVEMENT_BADGES = {
 		bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))',
 		frame: 'bronze',
 	},
+	'client60min': {
+		img: '/fluent-emoji/1f552.png',
+		bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))',
+		frame: 'silver',
+	},
 	'noteDeletedWithin1min': {
 		img: '/fluent-emoji/1f5d1.png',
 		bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))',
@@ -458,6 +464,7 @@ const claimingQueue = new Set<string>();
 
 export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) {
 	if ($i == null) return;
+	if ($i.movedTo) return;
 	if (claimedAchievements.includes(type)) return;
 	claimingQueue.add(type);
 	claimedAchievements.push(type);
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 1b47eaa42001aa0169ac811fdc92ff3d85b2e035..b6b7445b6721287b324b59f9bb603e5a205e8157 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -17,6 +17,7 @@ export function createAiScriptEnv(opts) {
 				title: title.value,
 				text: text.value,
 			});
+			return values.NULL;
 		}),
 		'Mk:confirm': values.FN_NATIVE(async ([title, text, type]) => {
 			const confirm = await os.confirm({
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index 52e610e437cb74f6c3ac18ccf6ac1de17021575a..ed01b490547c186e44abfabf7cabaee1107d3d32 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -36,6 +36,12 @@ function toggleSensitive(file: Misskey.entities.DriveFile) {
 	os.api('drive/files/update', {
 		fileId: file.id,
 		isSensitive: !file.isSensitive,
+	}).catch(err => {
+		os.alert({
+			type: 'error',
+			title: i18n.ts.error,
+			text: err.message,
+		});
 	});
 }
 
@@ -74,6 +80,12 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile) {
 		icon: 'ti ti-text-caption',
 		action: () => describe(file),
 	}, null, {
+		text: i18n.ts.createNoteFromTheFile,
+		icon: 'ti ti-pencil',
+		action: () => os.post({
+			initialFiles: [file],
+		}),
+	}, {
 		text: i18n.ts.copyUrl,
 		icon: 'ti ti-link',
 		action: () => copyUrl(file),
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index d91f0b0eb65662466c7b1e52096b962edd4696fe..c8a610025385a3cebb940abbeefc9a07885a5962 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -211,6 +211,12 @@ export function getNoteMenu(props: {
 		}, {}, 'closed');
 	}
 
+	function showRenotes(): void {
+		os.popup(defineAsyncComponent(() => import('@/components/MkRenotedUsersDialog.vue')), {
+			noteId: appearNote.id,
+		}, {}, 'closed');
+	}
+
 	async function translate(): Promise<void> {
 		if (props.translation.value != null) return;
 		props.translating.value = true;
@@ -241,8 +247,12 @@ export function getNoteMenu(props: {
 				text: i18n.ts.details,
 				action: openDetail,
 			}, {
-				icon: 'ti ti-users',
-				text: i18n.ts.reactions,
+				icon: 'ti ti-repeat',
+				text: i18n.ts.renotesList,
+				action: showRenotes,
+			}, {
+				icon: 'ti ti-icons',
+				text: i18n.ts.reactionsList,
 				action: showReactions,
 			}, {
 				icon: 'ti ti-copy',
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index fe941c77b27fa1fb9735420894a892f1545b4f46..6ff9fb63f1d92ab1717da4461be98aaeca9f3bc2 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -98,6 +98,27 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
 		});
 	}
 
+	async function editMemo(): Promise<void> {
+		const userDetailed = await os.api('users/show', {
+			userId: user.id,
+		});
+		const { canceled, result } = await os.form(i18n.ts.editMemo, {
+			memo: {
+				type: 'string',
+				required: true,
+				multiline: true,
+				label: i18n.ts.memo,
+				default: userDetailed.memo,
+			},
+		});
+		if (canceled) return;
+
+		os.apiWithDialog('users/update-memo', {
+			memo: result.memo,
+			userId: user.id,
+		});
+	}
+
 	let menu = [{
 		icon: 'ti ti-at',
 		text: i18n.ts.copyUsername,
@@ -123,6 +144,12 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
 			os.post({ specified: user, initialText: `@${user.username} ` });
 		},
 	}, null, {
+		icon: 'ti ti-pencil',
+		text: i18n.ts.editMemo,
+		action: () => {
+			editMemo();
+		},
+	}, {
 		type: 'parent',
 		icon: 'ti ti-list',
 		text: i18n.ts.addToList,
diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts
index b8fb853cc1a0a661b0028a1dad86b4d157d47070..c101a127f3d4257ea2baef753bac70f5e25fe48b 100644
--- a/packages/frontend/src/scripts/please-login.ts
+++ b/packages/frontend/src/scripts/please-login.ts
@@ -17,5 +17,5 @@ export function pleaseLogin(path?: string) {
 		},
 	}, 'closed');
 
-	if (!path) throw new Error('signin required');
+	throw new Error('signin required');
 }
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts
index ec5f8f65e91faeba628499849fe6510b524c81cc..fe9f0a2447ae22626a81b72c4f9fd18b50d1beb2 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/scripts/select-file.ts
@@ -6,70 +6,76 @@ import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
 import { uploadFile } from '@/scripts/upload';
 
-function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile | DriveFile[]> {
+export function chooseFileFromPc(multiple: boolean, keepOriginal = false): Promise<DriveFile[]> {
 	return new Promise((res, rej) => {
-		const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
-
-		const chooseFileFromPc = () => {
-			const input = document.createElement('input');
-			input.type = 'file';
-			input.multiple = multiple;
-			input.onchange = () => {
-				const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal.value));
+		const input = document.createElement('input');
+		input.type = 'file';
+		input.multiple = multiple;
+		input.onchange = () => {
+			const promises = Array.from(input.files).map(file => uploadFile(file, defaultStore.state.uploadFolder, undefined, keepOriginal));
 
-				Promise.all(promises).then(driveFiles => {
-					res(multiple ? driveFiles : driveFiles[0]);
-				}).catch(err => {
-					// アップロードのエラーは uploadFile 内でハンドリングされているためアラートダイアログを出したりはしてはいけない
-				});
+			Promise.all(promises).then(driveFiles => {
+				res(driveFiles);
+			}).catch(err => {
+				// アップロードのエラーは uploadFile 内でハンドリングされているためアラートダイアログを出したりはしてはいけない
+			});
 
-				// 一応廃棄
-				(window as any).__misskey_input_ref__ = null;
-			};
+			// 一応廃棄
+			(window as any).__misskey_input_ref__ = null;
+		};
 
-			// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
-			// iOS Safari で正常に動かす為のおまじない
-			(window as any).__misskey_input_ref__ = input;
+		// https://qiita.com/fukasawah/items/b9dc732d95d99551013d
+		// iOS Safari で正常に動かす為のおまじない
+		(window as any).__misskey_input_ref__ = input;
 
-			input.click();
-		};
+		input.click();
+	});
+}
 
-		const chooseFileFromDrive = () => {
-			os.selectDriveFile(multiple).then(files => {
-				res(files);
-			});
-		};
+export function chooseFileFromDrive(multiple: boolean): Promise<DriveFile[]> {
+	return new Promise((res, rej) => {
+		os.selectDriveFile(multiple).then(files => {
+			res(files);
+		});
+	});
+}
 
-		const chooseFileFromUrl = () => {
-			os.inputText({
-				title: i18n.ts.uploadFromUrl,
-				type: 'url',
-				placeholder: i18n.ts.uploadFromUrlDescription,
-			}).then(({ canceled, result: url }) => {
-				if (canceled) return;
+export function chooseFileFromUrl(): Promise<DriveFile> {
+	return new Promise((res, rej) => {
+		os.inputText({
+			title: i18n.ts.uploadFromUrl,
+			type: 'url',
+			placeholder: i18n.ts.uploadFromUrlDescription,
+		}).then(({ canceled, result: url }) => {
+			if (canceled) return;
 
-				const marker = Math.random().toString(); // TODO: UUIDとか使う
+			const marker = Math.random().toString(); // TODO: UUIDとか使う
 
-				const connection = stream.useChannel('main');
-				connection.on('urlUploadFinished', urlResponse => {
-					if (urlResponse.marker === marker) {
-						res(multiple ? [urlResponse.file] : urlResponse.file);
-						connection.dispose();
-					}
-				});
+			const connection = stream.useChannel('main');
+			connection.on('urlUploadFinished', urlResponse => {
+				if (urlResponse.marker === marker) {
+					res(urlResponse.file);
+					connection.dispose();
+				}
+			});
 
-				os.api('drive/files/upload-from-url', {
-					url: url,
-					folderId: defaultStore.state.uploadFolder,
-					marker,
-				});
+			os.api('drive/files/upload-from-url', {
+				url: url,
+				folderId: defaultStore.state.uploadFolder,
+				marker,
+			});
 
-				os.alert({
-					title: i18n.ts.uploadFromUrlRequested,
-					text: i18n.ts.uploadFromUrlMayTakeTime,
-				});
+			os.alert({
+				title: i18n.ts.uploadFromUrlRequested,
+				text: i18n.ts.uploadFromUrlMayTakeTime,
 			});
-		};
+		});
+	});
+}
+
+function select(src: any, label: string | null, multiple: boolean): Promise<DriveFile[]> {
+	return new Promise((res, rej) => {
+		const keepOriginal = ref(defaultStore.state.keepOriginalUploading);
 
 		os.popupMenu([label ? {
 			text: label,
@@ -81,23 +87,23 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Driv
 		}, {
 			text: i18n.ts.upload,
 			icon: 'ti ti-upload',
-			action: chooseFileFromPc,
+			action: () => chooseFileFromPc(multiple, keepOriginal.value).then(files => res(files)),
 		}, {
 			text: i18n.ts.fromDrive,
 			icon: 'ti ti-cloud',
-			action: chooseFileFromDrive,
+			action: () => chooseFileFromDrive(multiple).then(files => res(files)),
 		}, {
 			text: i18n.ts.fromUrl,
 			icon: 'ti ti-link',
-			action: chooseFileFromUrl,
+			action: () => chooseFileFromUrl().then(file => res([file])),
 		}], src);
 	});
 }
 
 export function selectFile(src: any, label: string | null = null): Promise<DriveFile> {
-	return select(src, label, false) as Promise<DriveFile>;
+	return select(src, label, false).then(files => files[0]);
 }
 
 export function selectFiles(src: any, label: string | null = null): Promise<DriveFile[]> {
-	return select(src, label, true) as Promise<DriveFile[]>;
+	return select(src, label, true);
 }
diff --git a/packages/frontend/src/scripts/show-moved-dialog.ts b/packages/frontend/src/scripts/show-moved-dialog.ts
new file mode 100644
index 0000000000000000000000000000000000000000..acb26c36e239c07cf049e8dfece0acb3a372debc
--- /dev/null
+++ b/packages/frontend/src/scripts/show-moved-dialog.ts
@@ -0,0 +1,16 @@
+import * as os from '@/os';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
+
+export function showMovedDialog() {
+	if (!$i) return;
+	if (!$i.movedTo) return;
+
+	os.alert({
+		type: 'error',
+		title: i18n.ts.accountMovedShort,
+		text: i18n.ts.operationForbidden,
+	});
+
+	throw new Error('account moved');
+}
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index 9a39652ef504c57b4836f2587eb8376c5acd176e..2dd11c9fa2783928f63e20d49bff357d8dbd5f2c 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -83,7 +83,13 @@ export function uploadFile(
 					// TODO: 消すのではなくて(ネットワーク的なエラーなら)再送できるようにしたい
 					uploads.value = uploads.value.filter(x => x.id !== id);
 
-					if (ev.target?.response) {
+					if (xhr.status === 413) {
+						alert({
+							type: 'error',
+							title: i18n.ts.failedToUpload,
+							text: i18n.ts.cannotUploadBecauseExceedsFileSizeLimit,
+						});
+					} else if (ev.target?.response) {
 						const res = JSON.parse(ev.target.response);
 						if (res.error?.id === 'bec5bd69-fba3-43c9-b4fb-2894b66ad5d2') {
 							alert({
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index a9350932403d29f0b684a9486f6868396c0ad2e0..245bcbefe11f40277bd9afae24ebb84be74481f2 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -38,7 +38,11 @@ export const pageViewInterruptors: PageViewInterruptor[] = [];
 // TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
 //       あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない
 export const defaultStore = markRaw(new Storage('base', {
-	tutorial: {
+	accountSetupWizard: {
+		where: 'account',
+		default: 0,
+	},
+	timelineTutorial: {
 		where: 'account',
 		default: 0,
 	},
@@ -164,7 +168,7 @@ export const defaultStore = markRaw(new Storage('base', {
 	},
 	animation: {
 		where: 'device',
-		default: !matchMedia('(prefers-reduced-motion)').matches,
+		default: !window.matchMedia('(prefers-reduced-motion)').matches,
 	},
 	animatedMfm: {
 		where: 'device',
@@ -182,9 +186,13 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	enableDataSaverMode: {
+		where: 'device',
+		default: false,
+	},
 	disableShowingAnimatedImages: {
 		where: 'device',
-		default: matchMedia('(prefers-reduced-motion)').matches,
+		default: window.matchMedia('(prefers-reduced-motion)').matches,
 	},
 	emojiStyle: {
 		where: 'device',
@@ -306,6 +314,22 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	mediaListWithOneImageAppearance: {
+		where: 'device',
+		default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3',
+	},
+	notificationPosition: {
+		where: 'device',
+		default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom',
+	},
+	notificationStackAxis: {
+		where: 'device',
+		default: 'horizontal' as 'vertical' | 'horizontal',
+	},
+	enableCondensedLineForAcct: {
+		where: 'device',
+		default: true,
+	},
 }));
 
 // TODO: 他のタブと永続化されたstateを同期
diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend/src/themes/d-botanical.json5
index c03b95e2d75854f8afd08d9ec805f72c255137d5..33cf7aa8174502b1226b9645d036bb56b2db6fca 100644
--- a/packages/frontend/src/themes/d-botanical.json5
+++ b/packages/frontend/src/themes/d-botanical.json5
@@ -13,8 +13,7 @@
 		fgHighlighted: '#fff',
 		divider: 'rgba(255, 255, 255, 0.14)',
 		panel: 'rgb(47, 47, 44)',
-		panelHeaderBg: '@panel',
-		panelHeaderDivider: '@divider',
+		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		header: ':alpha<0.7<@panel',
 		navBg: '#363636',
 		renote: '@accent',
diff --git a/packages/frontend/src/themes/d-dark.json5 b/packages/frontend/src/themes/d-dark.json5
index d24ce4df6952aa8d7c6fd377297d3baff13896f7..63144e88ea26f36cd9b551acda87b6e2ef292df6 100644
--- a/packages/frontend/src/themes/d-dark.json5
+++ b/packages/frontend/src/themes/d-dark.json5
@@ -13,8 +13,7 @@
 		fgHighlighted: '#fff',
 		divider: 'rgba(255, 255, 255, 0.14)',
 		panel: '#2d2d2d',
-		panelHeaderBg: '@panel',
-		panelHeaderDivider: '@divider',
+		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		header: ':alpha<0.7<@panel',
 		navBg: '#363636',
 		renote: '@accent',
diff --git a/packages/frontend/src/themes/d-future.json5 b/packages/frontend/src/themes/d-future.json5
index b6fa1ab0c18d43129f1a806d965e8629f2b18ead..0962a124110a4f655b7e1a312ea0ddd87e91f72a 100644
--- a/packages/frontend/src/themes/d-future.json5
+++ b/packages/frontend/src/themes/d-future.json5
@@ -14,8 +14,7 @@
 		fgOnAccent: '#000',
 		divider: 'rgba(255, 255, 255, 0.1)',
 		panel: '#18181c',
-		panelHeaderBg: '@panel',
-		panelHeaderDivider: '@divider',
+		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		renote: '@accent',
 		mention: '#f2c97d',
 		mentionMe: '@accent',
diff --git a/packages/frontend/src/themes/d-green-lime.json5 b/packages/frontend/src/themes/d-green-lime.json5
index a6983b9ac21a6db7dad2925bb82352a0fca0bf7d..9522f534a41b41fc4ff8f8db6009dc1dcef32726 100644
--- a/packages/frontend/src/themes/d-green-lime.json5
+++ b/packages/frontend/src/themes/d-green-lime.json5
@@ -14,8 +14,7 @@
 		fgOnAccent: '#192320',
 		divider: '#e7fffb24',
 		panel: '#192320',
-		panelHeaderBg: '@panel',
-		panelHeaderDivider: '@divider',
+		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		popup: '#293330',
 		renote: '@accent',
 		mentionMe: '#ffaa00',
diff --git a/packages/frontend/src/themes/d-green-orange.json5 b/packages/frontend/src/themes/d-green-orange.json5
index 62adc39e29cb9708d6959944352c7562951ca887..e542782c6651ac5870c7e7b6d9c91f5eb88a726e 100644
--- a/packages/frontend/src/themes/d-green-orange.json5
+++ b/packages/frontend/src/themes/d-green-orange.json5
@@ -14,8 +14,7 @@
 		fgOnAccent: '#192320',
 		divider: '#e7fffb24',
 		panel: '#192320',
-		panelHeaderBg: '@panel',
-		panelHeaderDivider: '@divider',
+		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		popup: '#293330',
 		renote: '@accent',
 		mentionMe: '#b4e900',
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index eae4f0091c783cc5fcf574e091b26eafe1e308cd..53042a4ce74ae43111a5d015377ca211a9d22b3b 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -76,7 +76,7 @@ export function openInstanceMenu(ev: MouseEvent) {
 		} : undefined],
 	}, null, {
 		text: i18n.ts.help,
-		icon: 'ti ti-question-circle',
+		icon: 'ti ti-help-circle',
 		action: () => {
 			window.open('https://misskey-hub.net/help.html', '_blank');
 		},
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index 5a32c076a41694e14e9f69338788f049ea4f753e..71a4285e9db12c95c580258c0ad4bb3849b6981b 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -10,14 +10,16 @@
 <XUpload v-if="uploads.length > 0"/>
 
 <TransitionGroup
-	tag="div" :class="$style.notifications"
+	tag="div" :class="[$style.notifications, $style[`notificationsPosition-${defaultStore.state.notificationPosition}`], $style[`notificationsStackAxis-${defaultStore.state.notificationStackAxis}`]]"
 	:move-class="defaultStore.state.animation ? $style.transition_notification_move : ''"
 	:enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''"
 	:leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''"
 	:enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''"
 	:leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''"
 >
-	<XNotification v-for="notification in notifications" :key="notification.id" :notification="notification" :class="$style.notification"/>
+	<div v-for="notification in notifications" :key="notification.id" :class="$style.notification">
+		<XNotification :notification="notification"/>
+	</div>
 </TransitionGroup>
 
 <XStreamIndicator/>
@@ -30,7 +32,7 @@
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
 import * as misskey from 'misskey-js';
 import { swInject } from './sw-inject';
 import XNotification from './notification.vue';
@@ -85,7 +87,10 @@ if ($i) {
 .transition_notification_leaveActive {
 	transition: opacity 0.3s, transform 0.3s !important;
 }
-.transition_notification_enterFrom,
+.transition_notification_enterFrom {
+	opacity: 0;
+	transform: translateX(250px);
+}
 .transition_notification_leaveTo {
 	opacity: 0;
 	transform: translateX(-250px);
@@ -94,35 +99,90 @@ if ($i) {
 .notifications {
 	position: fixed;
 	z-index: 3900000;
-	left: 0;
-	width: 250px;
-	top: 32px;
-	padding: 0 32px;
+	padding: 0 var(--margin);
 	pointer-events: none;
-	container-type: inline-size;
-}
+	display: flex;
 
-.notification {
-	& + .notification {
-		margin-top: 8px;
+	&.notificationsPosition-leftTop {
+		top: var(--margin);
+		left: 0;
+	}
+
+	&.notificationsPosition-rightTop {
+		top: var(--margin);
+		right: 0;
+	}
+
+	&.notificationsPosition-leftBottom {
+		bottom: calc(var(--minBottomSpacing) + var(--margin));
+		left: 0;
 	}
-}
 
-@media (max-width: 500px) {
-	.notifications {
-		top: initial;
+	&.notificationsPosition-rightBottom {
 		bottom: calc(var(--minBottomSpacing) + var(--margin));
-		padding: 0 var(--margin);
-		display: flex;
-		flex-direction: column-reverse;
+		right: 0;
 	}
 
-	.notification {
-		& + .notification {
-			margin-top: 0;
-			margin-bottom: 8px;
+	&.notificationsStackAxis-vertical {
+		width: 250px;
+
+		&.notificationsPosition-leftTop,
+		&.notificationsPosition-rightTop {
+			flex-direction: column;
+
+			.notification {
+				& + .notification {
+					margin-top: 8px;
+				}
+			}
+		}
+
+		&.notificationsPosition-leftBottom,
+		&.notificationsPosition-rightBottom {
+			flex-direction: column-reverse;
+
+			.notification {
+				& + .notification {
+					margin-bottom: 8px;
+				}
+			}
 		}
 	}
+
+	&.notificationsStackAxis-horizontal {
+		width: 100%;
+
+		&.notificationsPosition-leftTop,
+		&.notificationsPosition-leftBottom {
+			flex-direction: row;
+
+			.notification {
+				& + .notification {
+					margin-left: 8px;
+				}
+			}
+		}
+
+		&.notificationsPosition-rightTop,
+		&.notificationsPosition-rightBottom {
+			flex-direction: row-reverse;
+
+			.notification {
+				& + .notification {
+					margin-right: 8px;
+				}
+			}
+		}
+
+		.notification {
+			width: 250px;
+			flex-shrink: 0;
+		}
+	}
+}
+
+.notification {
+	container-type: inline-size;
 }
 </style>
 
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index ff0cba33ac65d6fd6b411de4dd2095ff5eb72ac6..9605d1b22ee6e029efb5d0f9c2302402210d1e87 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -40,7 +40,7 @@ if (props.column.channelId == null) {
 }
 
 async function setChannel() {
-	const channels = await os.api('channels/followed', {
+	const channels = await os.api('channels/my-favorites', {
 		limit: 100,
 	});
 	const { canceled, result: channel } = await os.select({
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index e895847bd96f60e6093676fbdc3b0fcdf888c3e0..402bbe035260e6a2e6fded26c9300310a6e5167f 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -22,7 +22,7 @@
 		<span :class="$style.title"><slot name="header"></slot></span>
 		<button v-tooltip="i18n.ts.settings" :class="$style.menu" class="_button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button>
 	</header>
-	<div v-show="active" ref="body" v-container :class="$style.body">
+	<div v-show="active" ref="body" :class="$style.body">
 		<slot></slot>
 	</div>
 </section>
@@ -243,7 +243,7 @@ function onDrop(ev) {
 <style lang="scss" module>
 .root {
 	--root-margin: 10px;
-	--deckColumnHeaderHeight: 40px;
+	--deckColumnHeaderHeight: 38px;
 
 	height: 100%;
 	overflow: clip;
@@ -318,10 +318,7 @@ function onDrop(ev) {
 	background: var(--panelHeaderBg);
 	box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
 	cursor: pointer;
-
-	&, * {
-		user-select: none;
-	}
+	user-select: none;
 }
 
 .title {
@@ -365,7 +362,7 @@ function onDrop(ev) {
 	overflow-x: clip;
 	-webkit-overflow-scrolling: touch;
 	box-sizing: border-box;
-	container-type: inline-size;
+	container-type: size;
 	background-color: var(--bg);
 }
 </style>
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 24629675156dc7ab42b9acee3f4953667ed0552e..27d0c26ac4a1147ab338f54a8fd91e4a33fd9c1b 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -2,7 +2,7 @@
 <div :class="[$style.root, { [$style.withWallpaper]: wallpaper }]">
 	<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
 
-	<MkStickyContainer v-container :class="$style.contents">
+	<MkStickyContainer :class="$style.contents">
 		<template #header><XStatusBars :class="$style.statusbars"/></template>
 		<main style="min-width: 0;" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
 			<div :class="$style.content" style="container-type: inline-size;">
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index d11649c603eeb8efc69c3db80a565d5f2ad61313..3e0c38bb831bd624cd14402b676346c467cc4b45 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -3,7 +3,7 @@
 	<XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
 
 	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
-	<button v-else class="_textButton mk-widget-edit" :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
+	<button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
 </div>
 </template>
 
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 6c96440ebd94820fe994850418caea8222781749..623abbda39dbdea2a11f5990ace54fc6689a2e14 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -1,19 +1,286 @@
 <template>
-<DesignB/>
+<div class="mk-app">
+	<a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
+
+	<div v-if="!narrow && !root" class="side">
+		<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
+		<div class="dashboard">
+			<MkVisitorDashboard/>
+		</div>
+	</div>
+
+	<div class="main">
+		<div v-if="!root" class="header">
+			<div v-if="narrow === false" class="wide">
+				<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA>
+				<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA>
+				<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA>
+				<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA>
+			</div>
+			<div v-else-if="narrow === true" class="narrow">
+				<button class="menu _button" @click="showMenu = true">
+					<i class="ti ti-menu-2 icon"></i>
+				</button>
+			</div>
+		</div>
+		<div class="contents">
+			<main v-if="!root" style="container-type: inline-size;">
+				<RouterView/>
+			</main>
+			<main v-else>
+				<RouterView/>
+			</main>
+		</div>
+	</div>
+
+	<Transition :name="'tray-back'">
+		<div
+			v-if="showMenu"
+			class="menu-back _modalBg"
+			@click="showMenu = false"
+			@touchstart.passive="showMenu = false"
+		></div>
+	</Transition>
+
+	<Transition :name="'tray'">
+		<div v-if="showMenu" class="menu">
+			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
+			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
+			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
+			<MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
+			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
+			<div class="divider"></div>
+			<MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
+			<MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA>
+			<MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA>
+			<div class="action">
+				<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
+				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
+			</div>
+		</div>
+	</Transition>
+</div>
 <XCommon/>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-//import DesignA from './visitor/a.vue';
-import DesignB from './visitor/b.vue';
+<script lang="ts" setup>
+import { ComputedRef, onMounted, provide } from 'vue';
 import XCommon from './_common_/common.vue';
+import { host, instanceName } from '@/config';
+import * as os from '@/os';
+import { instance } from '@/instance';
+import XSigninDialog from '@/components/MkSigninDialog.vue';
+import XSignupDialog from '@/components/MkSignupDialog.vue';
+import { ColdDeviceStorage, defaultStore } from '@/store';
+import { mainRouter } from '@/router';
+import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
+import { i18n } from '@/i18n';
+import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
+
+const DESKTOP_THRESHOLD = 1100;
+
+let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+
+provide('router', mainRouter);
+provideMetadataReceiver((info) => {
+	pageMetadata = info;
+	if (pageMetadata.value) {
+		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	}
+});
+
+const announcements = {
+	endpoint: 'announcements',
+	limit: 10,
+};
+
+const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
+
+let showMenu = $ref(false);
+let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
+let narrow = $ref(window.innerWidth < 1280);
+let meta = $ref();
 
-export default defineComponent({
-	components: {
-		XCommon,
-		//DesignA,
-		DesignB,
-	},
+const keymap = $computed(() => {
+	return {
+		'd': () => {
+			if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
+			defaultStore.set('darkMode', !defaultStore.state.darkMode);
+		},
+		's': () => {
+			mainRouter.push('/search');
+		},
+	};
+});
+
+const root = $computed(() => mainRouter.currentRoute.value.name === 'index');
+
+os.api('meta', { detail: true }).then(res => {
+	meta = res;
+});
+
+function signin() {
+	os.popup(XSigninDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+function signup() {
+	os.popup(XSignupDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+onMounted(() => {
+	if (!isDesktop) {
+		window.addEventListener('resize', () => {
+			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true;
+		}, { passive: true });
+	}
+});
+
+defineExpose({
+	showMenu: $$(showMenu),
 });
 </script>
+
+<style>
+.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
+</style>
+
+<style lang="scss" scoped>
+.tray-enter-active,
+.tray-leave-active {
+	opacity: 1;
+	transform: translateX(0);
+	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.tray-enter-from,
+.tray-leave-active {
+	opacity: 0;
+	transform: translateX(-240px);
+}
+
+.tray-back-enter-active,
+.tray-back-leave-active {
+	opacity: 1;
+	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.tray-back-enter-from,
+.tray-back-leave-active {
+	opacity: 0;
+}
+
+.mk-app {
+	display: flex;
+	min-height: 100vh;
+
+	> .side {
+		position: sticky;
+		top: 0;
+		left: 0;
+		width: 500px;
+		height: 100vh;
+		background: var(--accent);
+
+		> .banner {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 100%;
+			aspect-ratio: 1.5;
+			background-position: center;
+			background-size: cover;
+			-webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
+			mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
+		}
+
+		> .dashboard {
+			position: relative;
+			padding: 32px;
+			box-sizing: border-box;
+			max-height: 100%;
+			overflow: auto;
+		}
+	}
+
+	> .main {
+		flex: 1;
+		min-width: 0;
+
+		> .header {
+			background: var(--panel);
+
+			> .wide {
+				line-height: 50px;
+				padding: 0 16px;
+
+				> .link {
+					padding: 0 16px;
+				}
+			}
+
+			> .narrow {
+				> .menu {
+					padding: 16px;
+				}
+			}
+		}
+	}
+
+	> .menu-back {
+		position: fixed;
+		z-index: 1001;
+		top: 0;
+		left: 0;
+		width: 100vw;
+		height: 100vh;
+	}
+
+	> .menu {
+		position: fixed;
+		z-index: 1001;
+		top: 0;
+		left: 0;
+		width: 240px;
+		height: 100vh;
+		background: var(--panel);
+
+		> .link {
+			display: block;
+			padding: 16px;
+
+			> .icon {
+				margin-right: 1em;
+			}
+		}
+
+		> .divider {
+			margin: 8px auto;
+			width: calc(100% - 32px);
+			border-top: solid 0.5px var(--divider);
+		}
+
+		> .action {
+			padding: 16px;
+
+			> button {
+				display: block;
+				width: 100%;
+				padding: 10px;
+				box-sizing: border-box;
+				text-align: center;
+				border-radius: 999px;
+
+				&._button {
+					background: var(--panel);
+				}
+
+				&:first-child {
+					margin-bottom: 16px;
+				}
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/ui/visitor/a.vue b/packages/frontend/src/ui/visitor/a.vue
deleted file mode 100644
index 4761036075b15dd92d76afa25cd8ca0cc719ec7d..0000000000000000000000000000000000000000
--- a/packages/frontend/src/ui/visitor/a.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-<template>
-<div class="mk-app">
-	<div v-if="mainRouter.currentRoute?.name === 'index'" class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
-		<div>
-			<h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1>
-			<div v-if="meta" class="about">
-				<!-- eslint-disable-next-line vue/no-v-html -->
-				<div class="desc" v-html="meta.description || i18n.ts.introMisskey"></div>
-			</div>
-			<div class="action">
-				<button class="_button primary" @click="signup()">{{ i18n.ts.signup }}</button>
-				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
-			</div>
-		</div>
-	</div>
-	<div v-else class="banner-mini" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
-		<div>
-			<h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1>
-		</div>
-	</div>
-
-	<div class="main">
-		<div ref="contents" class="contents" :class="{ wallpaper }">
-			<header v-show="mainRouter.currentRoute?.name !== 'index'" ref="header" class="header">
-				<XHeader :info="pageInfo"/>
-			</header>
-			<main ref="main" style="container-type: inline-size;">
-				<RouterView/>
-			</main>
-			<div class="powered-by">
-				<b><MkA to="/">{{ host }}</MkA></b>
-				<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
-			</div>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XHeader from './header.vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import MkButton from '@/components/MkButton.vue';
-import { defaultStore, ColdDeviceStorage } from '@/store';
-import { mainRouter } from '@/router';
-import { instance } from '@/instance';
-import { i18n } from '@/i18n';
-
-const DESKTOP_THRESHOLD = 1100;
-
-export default defineComponent({
-	components: {
-		XHeader,
-		MkButton,
-	},
-
-	data() {
-		return {
-			host,
-			instanceName,
-			pageInfo: null,
-			meta: null,
-			narrow: window.innerWidth < 1280,
-			announcements: {
-				endpoint: 'announcements',
-				limit: 10,
-			},
-			mainRouter,
-			isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
-			defaultStore,
-			instance,
-			i18n,
-		};
-	},
-
-	computed: {
-		keymap(): any {
-			return {
-				'd': () => {
-					if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
-					this.defaultStore.set('darkMode', !this.defaultStore.state.darkMode);
-				},
-				's': () => {
-					mainRouter.push('/search');
-				},
-				'h|/': this.help,
-			};
-		},
-	},
-
-	created() {
-		document.documentElement.style.overflowY = 'scroll';
-
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-	},
-
-	mounted() {
-		if (!this.isDesktop) {
-			window.addEventListener('resize', () => {
-				if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
-			}, { passive: true });
-		}
-	},
-
-	methods: {
-		// @ThatOneCalculator: Are these methods even used?
-		// I can't find references to them anywhere else in the code...
-
-		// setParallax(el) {
-		// 	new simpleParallax(el);
-		// },
-
-		changePage(page) {
-			if (page == null) return;
-			// eslint-disable-next-line no-undef
-			if (page[symbols.PAGE_INFO]) {
-				// eslint-disable-next-line no-undef
-				this.pageInfo = page[symbols.PAGE_INFO];
-			}
-		},
-
-		top() {
-			window.scroll({ top: 0, behavior: 'smooth' });
-		},
-
-		help() {
-			window.open('https://misskey-hub.net/docs/keyboard-shortcut.md', '_blank');
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-app {
-	min-height: 100vh;
-
-	> .banner {
-		position: relative;
-		width: 100%;
-		text-align: center;
-		background-position: center;
-		background-size: cover;
-
-		> div {
-			height: 100%;
-			background: rgba(0, 0, 0, 0.3);
-
-			* {
-				color: #fff;
-			}
-					
-			> h1 {
-				margin: 0;
-				padding: 96px 32px 0 32px;
-				text-shadow: 0 0 8px black;
-
-				> .logo {
-					vertical-align: bottom;
-					max-height: 150px;
-				}
-			}
-
-			> .about {
-				padding: 32px;
-				max-width: 580px;
-				margin: 0 auto;
-				box-sizing: border-box;
-				text-shadow: 0 0 8px black;
-			}
-
-			> .action {
-				padding-bottom: 64px;
-				
-				> button {
-					display: inline-block;
-					padding: 10px 20px;
-					box-sizing: border-box;
-					text-align: center;
-					border-radius: 999px;
-					background: var(--panel);
-					color: var(--fg);
-
-					&.primary {
-						background: var(--accent);
-						color: #fff;
-					}
-
-					&:first-child {
-						margin-right: 16px;
-					}
-				}
-			}
-		}
-	}
-
-	> .banner-mini {
-		position: relative;
-		width: 100%;
-		text-align: center;
-		background-position: center;
-		background-size: cover;
-
-		> div {
-			position: relative;
-			z-index: 1;
-			height: 100%;
-			background: rgba(0, 0, 0, 0.3);
-
-			* {
-				color: #fff !important;
-			}
-
-			> header {
-				
-			}
-					
-			> h1 {
-				margin: 0;
-				padding: 32px;
-				text-shadow: 0 0 8px black;
-
-				> .logo {
-					vertical-align: bottom;
-					max-height: 100px;
-				}
-			}
-		}
-	}
-
-	> .main {
-		> .contents {
-			position: relative;
-			z-index: 1;
-
-			> .header {
-				position: sticky;
-				top: 0;
-				left: 0;
-				z-index: 1000;
-			}
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid var(--divider);
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-}
-</style>
-
-<style lang="scss">
-</style>
diff --git a/packages/frontend/src/ui/visitor/b.vue b/packages/frontend/src/ui/visitor/b.vue
deleted file mode 100644
index 5287a670c51c503e97c0bcba064be201d658bc62..0000000000000000000000000000000000000000
--- a/packages/frontend/src/ui/visitor/b.vue
+++ /dev/null
@@ -1,266 +0,0 @@
-<template>
-<div class="mk-app">
-	<a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
-
-	<div v-if="!narrow && !root" class="side">
-		<XKanban class="kanban" full/>
-	</div>
-
-	<div class="main">
-		<XKanban v-if="narrow && !root" class="banner" :powered-by="root"/>
-
-		<div class="contents">
-			<XHeader v-if="!root" class="header"/>
-			<main v-if="!root" style="container-type: inline-size;">
-				<RouterView/>
-			</main>
-			<main v-else>
-				<RouterView/>
-			</main>
-			<div v-if="!root" class="powered-by">
-				<b><MkA to="/">{{ host }}</MkA></b>
-				<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
-			</div>
-		</div>
-	</div>
-
-	<Transition :name="'tray-back'">
-		<div
-			v-if="showMenu"
-			class="menu-back _modalBg"
-			@click="showMenu = false"
-			@touchstart.passive="showMenu = false"
-		></div>
-	</Transition>
-
-	<Transition :name="'tray'">
-		<div v-if="showMenu" class="menu">
-			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
-			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
-			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
-			<MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
-			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
-			<div class="divider"></div>
-			<MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
-			<MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA>
-			<MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA>
-			<div class="action">
-				<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
-				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
-			</div>
-		</div>
-	</Transition>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { ComputedRef, onMounted, provide } from 'vue';
-import XHeader from './header.vue';
-import XKanban from './kanban.vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import { instance } from '@/instance';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import { ColdDeviceStorage, defaultStore } from '@/store';
-import { mainRouter } from '@/router';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
-import { i18n } from '@/i18n';
-
-const DESKTOP_THRESHOLD = 1100;
-
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-
-provide('router', mainRouter);
-provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
-	}
-});
-
-const announcements = {
-	endpoint: 'announcements',
-	limit: 10,
-};
-
-const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
-
-let showMenu = $ref(false);
-let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
-let narrow = $ref(window.innerWidth < 1280);
-let meta = $ref();
-
-const keymap = $computed(() => {
-	return {
-		'd': () => {
-			if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
-			defaultStore.set('darkMode', !defaultStore.state.darkMode);
-		},
-		's': () => {
-			mainRouter.push('/search');
-		},
-	};
-});
-
-const root = $computed(() => mainRouter.currentRoute.value.name === 'index');
-
-os.api('meta', { detail: true }).then(res => {
-	meta = res;
-});
-
-function signin() {
-	os.popup(XSigninDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-function signup() {
-	os.popup(XSignupDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-onMounted(() => {
-	if (!isDesktop) {
-		window.addEventListener('resize', () => {
-			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true;
-		}, { passive: true });
-	}
-});
-
-defineExpose({
-	showMenu: $$(showMenu),
-});
-</script>
-
-<style>
-.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
-</style>
-
-<style lang="scss" scoped>
-.tray-enter-active,
-.tray-leave-active {
-	opacity: 1;
-	transform: translateX(0);
-	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.tray-enter-from,
-.tray-leave-active {
-	opacity: 0;
-	transform: translateX(-240px);
-}
-
-.tray-back-enter-active,
-.tray-back-leave-active {
-	opacity: 1;
-	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
-}
-.tray-back-enter-from,
-.tray-back-leave-active {
-	opacity: 0;
-}
-
-.mk-app {
-	display: flex;
-	min-height: 100vh;
-	background-position: center;
-	background-size: cover;
-	background-attachment: fixed;
-
-	> .side {
-		width: 500px;
-		height: 100vh;
-
-		> .kanban {
-			position: fixed;
-			top: 0;
-			left: 0;
-			width: 500px;
-			height: 100vh;
-			overflow: auto;
-		}
-	}
-
-	> .main {
-		flex: 1;
-		min-width: 0;
-
-		> .banner {
-		}
-
-		> .contents {
-			position: relative;
-			z-index: 1;
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid var(--divider);
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-
-	> .menu-back {
-		position: fixed;
-		z-index: 1001;
-		top: 0;
-		left: 0;
-		width: 100vw;
-		height: 100vh;
-	}
-
-	> .menu {
-		position: fixed;
-		z-index: 1001;
-		top: 0;
-		left: 0;
-		width: 240px;
-		height: 100vh;
-		background: var(--panel);
-
-		> .link {
-			display: block;
-			padding: 16px;
-
-			> .icon {
-				margin-right: 1em;
-			}
-		}
-
-		> .divider {
-			margin: 8px auto;
-			width: calc(100% - 32px);
-			border-top: solid 0.5px var(--divider);
-		}
-
-		> .action {
-			padding: 16px;
-
-			> button {
-				display: block;
-				width: 100%;
-				padding: 10px;
-				box-sizing: border-box;
-				text-align: center;
-				border-radius: 999px;
-
-				&._button {
-					background: var(--panel);
-				}
-
-				&:first-child {
-					margin-bottom: 16px;
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/ui/visitor/header.vue b/packages/frontend/src/ui/visitor/header.vue
deleted file mode 100644
index 7de81f6431aae9c4b9a89438845205ad895fe8a6..0000000000000000000000000000000000000000
--- a/packages/frontend/src/ui/visitor/header.vue
+++ /dev/null
@@ -1,211 +0,0 @@
-<template>
-<div class="sqxihjet">
-	<div v-if="narrow === false" class="wide">
-		<div class="content">
-			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
-			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
-			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
-			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
-			<div class="right">
-				<button class="_button search" @click="search()"><i class="ti ti-search icon"></i><span>{{ i18n.ts.search }}</span></button>
-				<button class="_buttonPrimary signup" @click="signup()">{{ i18n.ts.signup }}</button>
-				<button class="_button login" @click="signin()">{{ i18n.ts.login }}</button>
-			</div>
-		</div>
-	</div>
-	<div v-else-if="narrow === true" class="narrow">
-		<button class="menu _button" @click="$parent.showMenu = true">
-			<i class="ti ti-menu-2 icon"></i>
-		</button>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import * as os from '@/os';
-import { instance } from '@/instance';
-import { mainRouter } from '@/router';
-import { i18n } from '@/i18n';
-
-export default defineComponent({
-	data() {
-		return {
-			narrow: null,
-			showMenu: false,
-			isTimelineAvailable: instance.policies.ltlAvailable || instance.policies.gtlAvailable,
-			i18n,
-		};
-	},
-
-	mounted() {
-		this.narrow = this.$el.clientWidth < 1300;
-	},
-
-	methods: {
-		signin() {
-			os.popup(XSigninDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		signup() {
-			os.popup(XSignupDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		search() {
-			mainRouter.push('/search');
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.sqxihjet {
-	$height: 50px;
-	position: sticky;
-	width: 50px;
-	top: 0;
-	left: 0;
-	z-index: 1000;
-	line-height: $height;
-	-webkit-backdrop-filter: var(--blur, blur(32px));
-	backdrop-filter: var(--blur, blur(32px));
-	background-color: var(--X16);
-
-	> .wide {
-		> .content {
-			max-width: 1400px;
-			margin: 0 auto;
-			display: flex;
-			align-items: center;
-
-			> .link {
-				$line: 3px;
-				display: inline-block;
-				padding: 0 16px;
-				line-height: $height - ($line * 2);
-				border-top: solid $line transparent;
-				border-bottom: solid $line transparent;
-
-				> .icon {
-					margin-right: 0.5em;
-				}
-
-				&.page {
-					border-bottom-color: var(--accent);
-				}
-			}
-
-			> .page {
-				> .title {
-					display: inline-block;
-					vertical-align: bottom;
-					white-space: nowrap;
-					overflow: hidden;
-					text-overflow: ellipsis;
-					position: relative;
-
-					> .icon + .text {
-						margin-left: 8px;
-					}
-
-					> .avatar {
-						$size: 32px;
-						display: inline-block;
-						width: $size;
-						height: $size;
-						vertical-align: middle;
-						margin-right: 8px;
-						pointer-events: none;
-					}
-
-					&._button {
-						&:hover {
-							color: var(--fgHighlighted);
-						}
-					}
-
-					&.selected {
-						box-shadow: 0 -2px 0 0 var(--accent) inset;
-						color: var(--fgHighlighted);
-					}
-				}
-
-				> .action {
-					padding: 0 0 0 16px;
-				}
-			}
-
-			> .right {
-				margin-left: auto;
-
-				> .search {
-					background: var(--bg);
-					border-radius: 999px;
-					width: 230px;
-					line-height: $height - 20px;
-					margin-right: 16px;
-					text-align: left;
-
-					> * {
-						opacity: 0.7;
-					}
-
-					> .icon {
-						padding: 0 16px;
-					}
-				}
-
-				> .signup {
-					border-radius: 999px;
-					padding: 0 24px;
-					line-height: $height - 20px;
-				}
-
-				> .login {
-					padding: 0 16px;
-				}
-			}
-		}
-	}
-
-	> .narrow {
-		display: flex;
-
-		> .menu,
-		> .action {
-			width: $height;
-			height: $height;
-			font-size: 20px;
-		}
-
-		> .title {
-			flex: 1;
-			white-space: nowrap;
-			overflow: hidden;
-			text-overflow: ellipsis;
-			position: relative;
-			text-align: center;
-
-			> .icon + .text {
-				margin-left: 8px;
-			}
-
-			> .avatar {
-				$size: 32px;
-				display: inline-block;
-				width: $size;
-				height: $size;
-				vertical-align: middle;
-				margin-right: 8px;
-				pointer-events: none;
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/ui/visitor/kanban.vue b/packages/frontend/src/ui/visitor/kanban.vue
deleted file mode 100644
index ce7fcfe94468993562d6efe62fac79eecc6876c5..0000000000000000000000000000000000000000
--- a/packages/frontend/src/ui/visitor/kanban.vue
+++ /dev/null
@@ -1,261 +0,0 @@
-<!-- eslint-disable vue/no-v-html -->
-<template>
-<div class="rwqkcmrc" :style="{ backgroundImage: transparent ? 'none' : `url(${ instance.backgroundImageUrl })` }">
-	<div class="back" :class="{ transparent }"></div>
-	<div class="contents">
-		<div class="wrapper">
-			<h1 v-if="meta" :class="{ full }">
-				<MkA to="/" class="link"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl" alt="logo"><span v-else class="text">{{ instanceName }}</span></MkA>
-			</h1>
-			<template v-if="full">
-				<div v-if="meta" class="about">
-					<div class="desc" v-html="meta.description || i18n.ts.introMisskey"></div>
-				</div>
-				<div class="action">
-					<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
-					<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
-				</div>
-				<div class="announcements panel">
-					<header>{{ i18n.ts.announcements }}</header>
-					<MkPagination v-slot="{items}" :pagination="announcements" class="list">
-						<section v-for="announcement in items" :key="announcement.id" class="item">
-							<div class="title">{{ announcement.title }}</div>
-							<div class="content">
-								<Mfm :text="announcement.text"/>
-								<img v-if="announcement.imageUrl" :src="announcement.imageUrl" alt="announcement image"/>
-							</div>
-						</section>
-					</MkPagination>
-				</div>
-				<div v-if="poweredBy" class="powered-by">
-					<b><MkA to="/">{{ host }}</MkA></b>
-					<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
-				</div>
-			</template>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import MkPagination from '@/components/MkPagination.vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/MkButton.vue';
-import { instance } from '@/instance';
-import { i18n } from '@/i18n';
-
-export default defineComponent({
-	components: {
-		MkPagination,
-		MkButton,
-	},
-
-	props: {
-		full: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		transparent: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		poweredBy: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
-
-	data() {
-		return {
-			host,
-			instanceName,
-			pageInfo: null,
-			meta: null,
-			narrow: window.innerWidth < 1280,
-			announcements: {
-				endpoint: 'announcements',
-				limit: 10,
-			},
-			instance,
-			i18n,
-		};
-	},
-
-	created() {
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-	},
-
-	methods: {
-		signin() {
-			os.popup(XSigninDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		signup() {
-			os.popup(XSignupDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.rwqkcmrc {
-	position: relative;
-	text-align: center;
-	background-position: center;
-	background-size: cover;
-	// TODO: パララックスにしたい
-
-	> .back {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-		background: rgba(0, 0, 0, 0.3);
-
-		&.transparent {
-			-webkit-backdrop-filter: var(--blur, blur(12px));
-			backdrop-filter: var(--blur, blur(12px));
-		}
-	}
-
-	> .contents {
-		position: relative;
-		z-index: 1;
-		height: inherit;
-		overflow: auto;
-
-		> .wrapper {
-			max-width: 380px;
-			padding: 0 16px;
-			box-sizing: border-box;
-			margin: 0 auto;
-
-			> .panel {
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
-				background: rgba(0, 0, 0, 0.5);
-				border-radius: var(--radius);
-
-				&, * {
-					color: #fff !important;
-				}
-			}
-
-			> h1 {
-				display: block;
-				margin: 0;
-				padding: 32px 0 32px 0;
-				color: #fff;
-
-				&.full {
-					padding: 64px 0 0 0;
-
-					> .link {
-						> ::v-deep(.logo) {
-							max-height: 130px;
-						}
-					}
-				}
-
-				> .link {
-					display: block;
-
-					> ::v-deep(.logo) {
-						vertical-align: bottom;
-						max-height: 100px;
-					}
-				}
-			}
-
-			> .about {
-				display: block;
-				margin: 24px 0;
-				text-align: center;
-				box-sizing: border-box;
-				text-shadow: 0 0 8px black;
-				color: #fff;
-			}
-
-			> .action {
-				> button {
-					display: block;
-					width: 100%;
-					padding: 10px;
-					box-sizing: border-box;
-					text-align: center;
-					border-radius: 999px;
-
-					&._button {
-						background: var(--panel);
-					}
-
-					&:first-child {
-						margin-bottom: 16px;
-					}
-				}
-			}
-
-			> .announcements {
-				margin: 32px 0;
-				text-align: left;
-
-				> header {
-					padding: 12px 16px;
-					border-bottom: solid 1px rgba(255, 255, 255, 0.5);
-				}
-
-				> .list {
-					max-height: 300px;
-					overflow: auto;
-
-					> .item {
-						padding: 12px 16px;
-
-						& + .item {
-							border-top: solid 1px rgba(255, 255, 255, 0.5);
-						}
-
-						> .title {
-							font-weight: bold;
-						}
-
-						> .content {
-							> img {
-								max-width: 100%;
-							}
-						}
-					}
-				}
-			}
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid rgba(255, 255, 255, 0.5);
-				color: #fff;
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
index 4d582daa3c33aeebd05babd758466d405339520b..514b304246dfd2ada8edb8d6dfe1ebb1191b8359 100644
--- a/packages/frontend/tsconfig.json
+++ b/packages/frontend/tsconfig.json
@@ -27,10 +27,12 @@
 		},
 		"typeRoots": [
 			"node_modules/@types",
+			"node_modules/@vue-macros",
 			"@types",
 		],
 		"types": [
 			"vite/client",
+			"reactivity-transform/macros-global"
 		],
 		"lib": [
 			"esnext",
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 425f3aa45d93273a8ae6b3188b66d578f940d694..fad0dd0177a48088b8ec1562cd8afe636a751edb 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -1,6 +1,9 @@
 import path from 'path';
+import pluginReplace from '@rollup/plugin-replace';
 import pluginVue from '@vitejs/plugin-vue';
 import { type UserConfig, defineConfig } from 'vite';
+// @ts-expect-error https://github.com/sxzz/unplugin-vue-macros/issues/257#issuecomment-1410752890
+import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
 
 import locales from '../../locales';
 import meta from '../../package.json';
@@ -41,11 +44,26 @@ export function getConfig(): UserConfig {
 	return {
 		base: '/vite/',
 
+		server: {
+			port: 5173,
+		},
+
 		plugins: [
 			pluginVue({
 				reactivityTransform: true,
 			}),
+			ReactivityTransform(),
 			pluginJson5(),
+			...process.env.NODE_ENV === 'production'
+				? [
+					pluginReplace({
+						preventAssignment: true,
+						values: {
+							'isChromatic()': JSON.stringify(false),
+						},
+					}),
+				]
+				: [],
 		],
 
 		resolve: {
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 67d12000b865fdaed087e3af1bf9e73a4e899f3d..19e5b7544394ec112a55800ee9ccf660bf0f5f09 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -1357,10 +1357,6 @@ export type Endpoints = {
         req: TODO;
         res: TODO;
     };
-    'i/known-as': {
-        req: TODO;
-        res: TODO;
-    };
     'i/notifications': {
         req: {
             limit?: number;
@@ -1511,6 +1507,7 @@ export type Endpoints = {
             mutedWords?: string[][];
             mutingNotificationTypes?: Notification_2['type'][];
             emailNotificationTypes?: string[];
+            alsoKnownAs?: string[];
         };
         res: MeDetailed;
     };
@@ -2348,6 +2345,7 @@ type LiteInstanceMetadata = {
         imageUrl: string;
     }[];
     translatorAvailable: boolean;
+    serverRules: string[];
 };
 
 // @public (undocumented)
@@ -2633,6 +2631,7 @@ type User = UserLite | UserDetailed;
 
 // @public (undocumented)
 type UserDetailed = UserLite & {
+    alsoKnownAs: string[];
     bannerBlurhash: string | null;
     bannerColor: string | null;
     bannerUrl: string | null;
@@ -2663,6 +2662,7 @@ type UserDetailed = UserLite & {
     lang: string | null;
     lastFetchedAt?: DateString;
     location: string | null;
+    movedTo: string;
     notesCount: number;
     pinnedNoteIds: ID[];
     pinnedNotes: Note[];
@@ -2696,8 +2696,6 @@ type UserLite = {
     onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
     avatarUrl: string;
     avatarBlurhash: string;
-    alsoKnownAs: string[];
-    movedToUri: any;
     emojis: {
         name: string;
         url: string;
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 76e0452444e2041b48faaa9316f5e0acbdc7c035..2e9bc760593ed7853d58da263c8a041fa17ff22a 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -20,27 +20,27 @@
 		"url": "git+https://github.com/misskey-dev/misskey.js.git"
 	},
 	"devDependencies": {
-		"@microsoft/api-extractor": "7.34.4",
-		"@swc/jest": "0.2.24",
-		"@types/jest": "29.5.0",
-		"@types/node": "18.15.11",
-		"@typescript-eslint/eslint-plugin": "5.57.1",
-		"@typescript-eslint/parser": "5.57.1",
-		"eslint": "8.37.0",
+		"@microsoft/api-extractor": "7.34.7",
+		"@swc/jest": "0.2.26",
+		"@types/jest": "29.5.1",
+		"@types/node": "18.16.3",
+		"@typescript-eslint/eslint-plugin": "5.59.2",
+		"@typescript-eslint/parser": "5.59.2",
+		"eslint": "8.39.0",
 		"jest": "29.5.0",
 		"jest-fetch-mock": "3.0.3",
 		"jest-websocket-mock": "2.4.0",
 		"mock-socket": "9.2.1",
 		"tsd": "0.28.1",
-		"typescript": "5.0.3"
+		"typescript": "5.0.4"
 	},
 	"files": [
 		"built"
 	],
 	"dependencies": {
 		"@swc/cli": "0.1.62",
-		"@swc/core": "1.3.46",
-		"eventemitter3": "5.0.0",
+		"@swc/core": "1.3.56",
+		"eventemitter3": "5.0.1",
 		"reconnecting-websocket": "4.4.0"
 	}
 }
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index aed9f5bf84296a40108af70fa9ccf54ca31422bd..cc88c4b1a401eacd418f3d0a75191f4936e6adda 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -363,7 +363,6 @@ export type Endpoints = {
 	'i/import-following': { req: TODO; res: TODO; };
 	'i/import-user-lists': { req: TODO; res: TODO; };
 	'i/move': { req: TODO; res: TODO; };
-	'i/known-as': { req: TODO; res: TODO; };
 	'i/notifications': { req: {
 		limit?: number;
 		sinceId?: Notification['id'];
@@ -421,6 +420,7 @@ export type Endpoints = {
 		mutedWords?: string[][];
 		mutingNotificationTypes?: Notification['type'][];
 		emailNotificationTypes?: string[];
+		alsoKnownAs?: string[];
 	}; res: MeDetailed; };
 	'i/user-group-invites': { req: TODO; res: TODO; };
 	'i/2fa/done': { req: TODO; res: TODO; };
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 0c90e44494e229ff63409e6d03098b983a187788..04065c51c9ccb5956e8a497889920a5c085783d8 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -14,8 +14,6 @@ export type UserLite = {
 	onlineStatus: 'online' | 'active' | 'offline' | 'unknown';
 	avatarUrl: string;
 	avatarBlurhash: string;
-	alsoKnownAs: string[];
-	movedToUri: any;
 	emojis: {
 		name: string;
 		url: string;
@@ -31,6 +29,7 @@ export type UserLite = {
 };
 
 export type UserDetailed = UserLite & {
+	alsoKnownAs: string[];
 	bannerBlurhash: string | null;
 	bannerColor: string | null;
 	bannerUrl: string | null;
@@ -58,6 +57,7 @@ export type UserDetailed = UserLite & {
 	lang: string | null;
 	lastFetchedAt?: DateString;
 	location: string | null;
+	movedTo: string;
 	notesCount: number;
 	pinnedNoteIds: ID[];
 	pinnedNotes: Note[];
@@ -315,6 +315,7 @@ export type LiteInstanceMetadata = {
 		imageUrl: string;
 	}[];
 	translatorAvailable: boolean;
+	serverRules: string[];
 };
 
 export type DetailedInstanceMetadata = LiteInstanceMetadata & {
diff --git a/packages/sw/package.json b/packages/sw/package.json
index d7a4a2a4b6fe092154915e4e5b828f1988c2b249..75bb40ccb10b1c6c19c14c07db51b68b0cd22a35 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -9,15 +9,15 @@
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
 	"dependencies": {
-		"esbuild": "0.17.15",
+		"esbuild": "0.17.18",
 		"idb-keyval": "6.2.0",
 		"misskey-js": "workspace:*"
 	},
 	"devDependencies": {
-		"@typescript-eslint/parser": "5.57.1",
+		"@typescript-eslint/parser": "5.59.2",
 		"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
-		"eslint": "8.37.0",
+		"eslint": "8.39.0",
 		"eslint-plugin-import": "2.27.5",
-		"typescript": "5.0.3"
+		"typescript": "5.0.4"
 	}
 }
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index 9e0d9f0d1e888e89f0be72e42a43ba00e7e8286d..2783f2bfb5f8db9506599c44905b7a7dad9b57b5 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -56,7 +56,7 @@ globalThis.addEventListener('push', ev => {
 				return createNotification(data);
 			case 'readAllNotifications':
 				await globalThis.registration.getNotifications()
-					.then(notifications => notifications.forEach(n => n.close()));
+					.then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close()));
 				break;
 		}
 
@@ -133,7 +133,7 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
 				switch (action) {
 					case 'markAllAsRead':
 						await globalThis.registration.getNotifications()
-							.then(notifications => notifications.forEach(n => n.close()));
+							.then(notifications => notifications.forEach(n => n.tag !== 'read_notification' && n.close()));
 						await get('accounts').then(accounts => {
 							return Promise.all(accounts.map(async account => {
 								await swos.sendMarkAllAsRead(account.id);
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7325659f8030047fed6d1b484e823ae99a8910e3..04d2fcf36b12eb2865ed5249339f8d0b5a1046bc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -30,12 +30,12 @@ importers:
         specifier: 4.1.0
         version: 4.1.0
       typescript:
-        specifier: 5.0.3
-        version: 5.0.3
+        specifier: 5.0.4
+        version: 5.0.4
     optionalDependencies:
       '@tensorflow/tfjs-core':
-        specifier: 4.2.0
-        version: 4.2.0
+        specifier: 4.4.0
+        version: 4.4.0
     devDependencies:
       '@types/gulp':
         specifier: 4.0.10
@@ -44,20 +44,20 @@ importers:
         specifier: 2.0.1
         version: 2.0.1
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.57.1
-        version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4)
       '@typescript-eslint/parser':
-        specifier: 5.57.1
-        version: 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 12.9.0
-        version: 12.9.0
+        specifier: 12.11.0
+        version: 12.11.0
       eslint:
-        specifier: 8.37.0
-        version: 8.37.0
+        specifier: 8.39.0
+        version: 8.39.0
       start-server-and-test:
         specifier: 2.0.0
         version: 2.0.0
@@ -65,23 +65,23 @@ importers:
   packages/backend:
     dependencies:
       '@aws-sdk/client-s3':
-        specifier: 3.306.0
-        version: 3.306.0
+        specifier: 3.321.1
+        version: 3.321.1
       '@aws-sdk/lib-storage':
-        specifier: 3.306.0
-        version: 3.306.0(@aws-sdk/abort-controller@3.306.0)(@aws-sdk/client-s3@3.306.0)
+        specifier: 3.321.1
+        version: 3.321.1(@aws-sdk/abort-controller@3.310.0)(@aws-sdk/client-s3@3.321.1)
       '@aws-sdk/node-http-handler':
-        specifier: 3.306.0
-        version: 3.306.0
+        specifier: 3.321.1
+        version: 3.321.1
       '@bull-board/api':
-        specifier: 5.0.0
-        version: 5.0.0
+        specifier: 5.1.2
+        version: 5.1.2(@bull-board/ui@5.1.2)
       '@bull-board/fastify':
-        specifier: 5.0.0
-        version: 5.0.0
+        specifier: 5.1.2
+        version: 5.1.2
       '@bull-board/ui':
-        specifier: 5.0.0
-        version: 5.0.0
+        specifier: 5.1.2
+        version: 5.1.2
       '@discordapp/twemoji':
         specifier: 14.1.2
         version: 14.1.2
@@ -98,20 +98,20 @@ importers:
         specifier: 9.0.0
         version: 9.0.0
       '@fastify/multipart':
-        specifier: 7.5.0
-        version: 7.5.0
+        specifier: 7.6.0
+        version: 7.6.0
       '@fastify/static':
-        specifier: 6.10.0
-        version: 6.10.0
+        specifier: 6.10.1
+        version: 6.10.1
       '@fastify/view':
         specifier: 7.4.1
         version: 7.4.1
       '@nestjs/common':
         specifier: 9.4.0
-        version: 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0)
+        version: 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nestjs/core':
         specifier: 9.4.0
-        version: 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.0)
+        version: 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nestjs/testing':
         specifier: 9.4.0
         version: 9.4.0(@nestjs/common@9.4.0)(@nestjs/core@9.4.0)
@@ -123,10 +123,10 @@ importers:
         version: 10.0.2
       '@swc/cli':
         specifier: 0.1.62
-        version: 0.1.62(@swc/core@1.3.46)(chokidar@3.5.3)
+        version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3)
       '@swc/core':
-        specifier: 1.3.46
-        version: 1.3.46(@swc/helpers@0.5.0)
+        specifier: 1.3.56
+        version: 1.3.56
       accepts:
         specifier: 1.3.8
         version: 1.3.8
@@ -173,8 +173,8 @@ importers:
         specifier: 0.5.4
         version: 0.5.4
       date-fns:
-        specifier: 2.29.3
-        version: 2.29.3
+        specifier: 2.30.0
+        version: 2.30.0
       deep-email-validator:
         specifier: 0.1.21
         version: 0.1.21
@@ -182,14 +182,14 @@ importers:
         specifier: 0.0.1
         version: 0.0.1
       fastify:
-        specifier: 4.15.0
-        version: 4.15.0
+        specifier: 4.17.0
+        version: 4.17.0
       feed:
         specifier: 4.2.2
         version: 4.2.2
       file-type:
-        specifier: 18.2.1
-        version: 18.2.1
+        specifier: 18.3.0
+        version: 18.3.0
       fluent-ffmpeg:
         specifier: 2.1.2
         version: 2.1.2
@@ -200,14 +200,14 @@ importers:
         specifier: 12.6.0
         version: 12.6.0
       happy-dom:
-        specifier: 8.9.0
-        version: 8.9.0
+        specifier: 9.10.2
+        version: 9.10.2
       hpagent:
         specifier: 1.2.0
         version: 1.2.0
       ioredis:
-        specifier: 4.28.5
-        version: 4.28.5
+        specifier: 5.3.2
+        version: 5.3.2
       ip-cidr:
         specifier: 3.1.0
         version: 3.1.0
@@ -227,8 +227,11 @@ importers:
         specifier: 8.1.1
         version: 8.1.1
       jsrsasign:
-        specifier: 10.7.0
-        version: 10.7.0
+        specifier: 10.8.6
+        version: 10.8.6
+      meilisearch:
+        specifier: 0.32.3
+        version: 0.32.3
       mfm-js:
         specifier: 0.23.3
         version: 0.23.3
@@ -252,7 +255,7 @@ importers:
         version: 6.9.1
       nsfwjs:
         specifier: 2.4.2
-        version: 2.4.2(@tensorflow/tfjs@4.2.0)
+        version: 2.4.2(@tensorflow/tfjs@4.4.0)
       oauth:
         specifier: 0.10.0
         version: 0.10.0
@@ -287,8 +290,8 @@ importers:
         specifier: 0.3.17
         version: 0.3.17
       qrcode:
-        specifier: 1.5.1
-        version: 1.5.1
+        specifier: 1.5.3
+        version: 1.5.3
       random-seed:
         specifier: 0.3.0
         version: 0.3.0
@@ -311,11 +314,11 @@ importers:
         specifier: 1.0.0
         version: 1.0.0
       rss-parser:
-        specifier: 3.12.0
-        version: 3.12.0
+        specifier: 3.13.0
+        version: 3.13.0
       rxjs:
-        specifier: 7.8.0
-        version: 7.8.0
+        specifier: 7.8.1
+        version: 7.8.1
       s-age:
         specifier: 1.1.2
         version: 1.1.2
@@ -326,14 +329,17 @@ importers:
         specifier: 3.0.5
         version: 3.0.5
       semver:
-        specifier: 7.3.8
-        version: 7.3.8
+        specifier: 7.5.0
+        version: 7.5.0
       sharp:
-        specifier: 0.32.0
-        version: 0.32.0
+        specifier: 0.32.1
+        version: 0.32.1
       sharp-read-bmp:
         specifier: github:misskey-dev/sharp-read-bmp
         version: github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01
+      slacc:
+        specifier: 0.0.7
+        version: 0.0.7
       strict-event-emitter-types:
         specifier: 2.0.0
         version: 2.0.0
@@ -342,7 +348,7 @@ importers:
         version: 2.1.0
       summaly:
         specifier: github:misskey-dev/summaly
-        version: github.com/misskey-dev/summaly/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992
+        version: github.com/misskey-dev/summaly/2d63e2a0066f89871e777cc81d43c1ade8c97517
       systeminformation:
         specifier: 5.17.12
         version: 5.17.12
@@ -353,8 +359,8 @@ importers:
         specifier: 0.2.1
         version: 0.2.1
       tsc-alias:
-        specifier: 1.8.5
-        version: 1.8.5
+        specifier: 1.8.6
+        version: 1.8.6
       tsconfig-paths:
         specifier: 4.2.0
         version: 4.2.0
@@ -362,11 +368,11 @@ importers:
         specifier: 14.0.0
         version: 14.0.0
       typeorm:
-        specifier: 0.3.13
-        version: 0.3.13(ioredis@4.28.5)(pg@8.10.0)
+        specifier: 0.3.15
+        version: 0.3.15(ioredis@5.3.2)(pg@8.10.0)
       typescript:
-        specifier: 5.0.3
-        version: 5.0.3
+        specifier: 5.0.4
+        version: 5.0.4
       ulid:
         specifier: 2.3.0
         version: 2.3.0
@@ -380,8 +386,8 @@ importers:
         specifier: 1.1.2
         version: 1.1.2
       web-push:
-        specifier: 3.5.0
-        version: 3.5.0
+        specifier: 3.6.1
+        version: 3.6.1
       websocket:
         specifier: 1.0.34
         version: 1.0.34
@@ -396,48 +402,81 @@ importers:
         specifier: 1.3.11
         version: 1.3.11
       '@swc/core-darwin-arm64':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-darwin-x64':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-linux-arm-gnueabihf':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-linux-arm64-gnu':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-linux-arm64-musl':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-linux-x64-gnu':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-linux-x64-musl':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-win32-arm64-msvc':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-win32-ia32-msvc':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@swc/core-win32-x64-msvc':
-        specifier: 1.3.46
-        version: 1.3.46
+        specifier: 1.3.56
+        version: 1.3.56
       '@tensorflow/tfjs':
-        specifier: 4.2.0
-        version: 4.2.0(seedrandom@3.0.5)
+        specifier: 4.4.0
+        version: 4.4.0(seedrandom@3.0.5)
       '@tensorflow/tfjs-node':
-        specifier: 4.2.0
-        version: 4.2.0(seedrandom@3.0.5)
+        specifier: 4.4.0
+        version: 4.4.0(seedrandom@3.0.5)
+      slacc-android-arm-eabi:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-android-arm64:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-darwin-arm64:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-darwin-universal:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-darwin-x64:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-linux-arm-gnueabihf:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-linux-arm64-gnu:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-linux-arm64-musl:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-linux-x64-gnu:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-win32-arm64-msvc:
+        specifier: 0.0.7
+        version: 0.0.7
+      slacc-win32-x64-msvc:
+        specifier: 0.0.7
+        version: 0.0.7
     devDependencies:
       '@jest/globals':
         specifier: 29.5.0
         version: 29.5.0
       '@swc/jest':
-        specifier: 0.2.24
-        version: 0.2.24(@swc/core@1.3.46)
+        specifier: 0.2.26
+        version: 0.2.26(@swc/core@1.3.56)
       '@types/accepts':
         specifier: 1.3.5
         version: 1.3.5
@@ -465,12 +504,9 @@ importers:
       '@types/fluent-ffmpeg':
         specifier: 2.1.21
         version: 2.1.21
-      '@types/ioredis':
-        specifier: 4.28.10
-        version: 4.28.10
       '@types/jest':
-        specifier: 29.5.0
-        version: 29.5.0
+        specifier: 29.5.1
+        version: 29.5.1
       '@types/js-yaml':
         specifier: 4.0.5
         version: 4.0.5
@@ -487,8 +523,8 @@ importers:
         specifier: 2.1.1
         version: 2.1.1
       '@types/node':
-        specifier: 18.15.11
-        version: 18.15.11
+        specifier: 18.16.3
+        version: 18.16.3
       '@types/node-fetch':
         specifier: 3.0.3
         version: 3.0.3
@@ -529,8 +565,8 @@ importers:
         specifier: 7.3.13
         version: 7.3.13
       '@types/sharp':
-        specifier: 0.31.1
-        version: 0.31.1
+        specifier: 0.32.0
+        version: 0.32.0
       '@types/sinonjs__fake-timers':
         specifier: 8.1.2
         version: 8.1.2
@@ -559,11 +595,11 @@ importers:
         specifier: 8.5.4
         version: 8.5.4
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.57.1
-        version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4)
       '@typescript-eslint/parser':
-        specifier: 5.57.1
-        version: 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       aws-sdk-client-mock:
         specifier: ^2.1.1
         version: 2.1.1
@@ -571,17 +607,17 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       eslint:
-        specifier: 8.37.0
-        version: 8.37.0
+        specifier: 8.39.0
+        version: 8.39.0
       eslint-plugin-import:
         specifier: 2.27.5
-        version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)
+        version: 2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)
       execa:
         specifier: 6.1.0
         version: 6.1.0
       jest:
         specifier: 29.5.0
-        version: 29.5.0(@types/node@18.15.11)
+        version: 29.5.0(@types/node@18.16.3)
       jest-mock:
         specifier: 29.5.0
         version: 29.5.0
@@ -592,23 +628,29 @@ importers:
         specifier: 14.1.2
         version: 14.1.2
       '@rollup/plugin-alias':
-        specifier: 4.0.3
-        version: 4.0.3(rollup@3.20.2)
+        specifier: 5.0.0
+        version: 5.0.0(rollup@3.21.3)
       '@rollup/plugin-json':
         specifier: 6.0.0
-        version: 6.0.0(rollup@3.20.2)
+        version: 6.0.0(rollup@3.21.3)
+      '@rollup/plugin-replace':
+        specifier: ^5.0.2
+        version: 5.0.2(rollup@3.21.3)
       '@rollup/pluginutils':
         specifier: 5.0.2
-        version: 5.0.2(rollup@3.20.2)
+        version: 5.0.2(rollup@3.21.3)
       '@syuilo/aiscript':
-        specifier: 0.13.1
-        version: 0.13.1
+        specifier: 0.13.2
+        version: 0.13.2
       '@tabler/icons-webfont':
-        specifier: 2.12.0
-        version: 2.12.0
+        specifier: 2.17.0
+        version: 2.17.0
       '@vitejs/plugin-vue':
-        specifier: 4.1.0
-        version: 4.1.0(vite@4.2.1)(vue@3.2.47)
+        specifier: 4.2.1
+        version: 4.2.1(vite@4.3.4)(vue@3.2.47)
+      '@vue-macros/reactivity-transform':
+        specifier: ^0.3.5
+        version: 0.3.5(rollup@3.21.3)(vue@3.2.47)
       '@vue/compiler-sfc':
         specifier: 3.2.47
         version: 3.2.47
@@ -628,20 +670,23 @@ importers:
         specifier: 1.6.0
         version: 1.6.0
       chart.js:
-        specifier: 4.2.1
-        version: 4.2.1
+        specifier: 4.3.0
+        version: 4.3.0
       chartjs-adapter-date-fns:
         specifier: 3.0.0
-        version: 3.0.0(chart.js@4.2.1)(date-fns@2.29.3)
+        version: 3.0.0(chart.js@4.3.0)(date-fns@2.30.0)
       chartjs-chart-matrix:
         specifier: 2.0.1
-        version: 2.0.1(chart.js@4.2.1)
+        version: 2.0.1(chart.js@4.3.0)
       chartjs-plugin-gradient:
         specifier: 0.6.1
-        version: 0.6.1(chart.js@4.2.1)
+        version: 0.6.1(chart.js@4.3.0)
       chartjs-plugin-zoom:
         specifier: 2.0.1
-        version: 2.0.1(chart.js@4.2.1)
+        version: 2.0.1(chart.js@4.3.0)
+      chromatic:
+        specifier: 6.17.3
+        version: 6.17.3
       compare-versions:
         specifier: 5.0.1
         version: 5.0.1
@@ -649,14 +694,14 @@ importers:
         specifier: 2.0.0-beta.2
         version: 2.0.0-beta.2
       date-fns:
-        specifier: 2.29.3
-        version: 2.29.3
+        specifier: 2.30.0
+        version: 2.30.0
       escape-regexp:
         specifier: 0.0.1
         version: 0.0.1
       eventemitter3:
-        specifier: 5.0.0
-        version: 5.0.0
+        specifier: 5.0.1
+        version: 5.0.1
       gsap:
         specifier: 3.11.5
         version: 3.11.5
@@ -697,8 +742,8 @@ importers:
         specifier: 1.0.0
         version: 1.0.0
       rollup:
-        specifier: 3.20.2
-        version: 3.20.2
+        specifier: 3.21.3
+        version: 3.21.3
       s-age:
         specifier: 1.1.2
         version: 1.1.2
@@ -706,8 +751,8 @@ importers:
         specifier: 2.10.0
         version: 2.10.0
       sass:
-        specifier: 1.60.0
-        version: 1.60.0
+        specifier: 1.62.1
+        version: 1.62.1
       seedrandom:
         specifier: 3.0.5
         version: 3.0.5
@@ -730,8 +775,8 @@ importers:
         specifier: 1.6.0
         version: 1.6.0
       tsc-alias:
-        specifier: 1.8.5
-        version: 1.8.5
+        specifier: 1.8.6
+        version: 1.8.6
       tsconfig-paths:
         specifier: 4.2.0
         version: 4.2.0
@@ -739,8 +784,8 @@ importers:
         specifier: 14.0.0
         version: 14.0.0
       typescript:
-        specifier: 5.0.3
-        version: 5.0.3
+        specifier: 5.0.4
+        version: 5.0.4
       uuid:
         specifier: 9.0.0
         version: 9.0.0
@@ -748,8 +793,8 @@ importers:
         specifier: 1.8.0
         version: 1.8.0
       vite:
-        specifier: 4.2.1
-        version: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+        specifier: 4.3.4
+        version: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
       vue:
         specifier: 3.2.47
         version: 3.2.47
@@ -763,57 +808,60 @@ importers:
         specifier: next
         version: 4.1.0(vue@3.2.47)
     devDependencies:
+      '@storybook/addon-actions':
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-essentials':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-interactions':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-links':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-storysource':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addons':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/blocks':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/core-events':
-        specifier: 7.0.2
-        version: 7.0.2
+        specifier: 7.0.7
+        version: 7.0.7
       '@storybook/jest':
         specifier: 0.1.0
         version: 0.1.0
       '@storybook/manager-api':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/preview-api':
-        specifier: 7.0.2
-        version: 7.0.2
+        specifier: 7.0.7
+        version: 7.0.7
       '@storybook/react':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)
       '@storybook/react-vite':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4)
       '@storybook/testing-library':
-        specifier: 0.0.14-next.1
-        version: 0.0.14-next.1
+        specifier: 0.1.0
+        version: 0.1.0
       '@storybook/theming':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)
       '@storybook/types':
-        specifier: 7.0.2
-        version: 7.0.2
+        specifier: 7.0.7
+        version: 7.0.7
       '@storybook/vue3':
-        specifier: 7.0.2
-        version: 7.0.2(vue@3.2.47)
+        specifier: 7.0.7
+        version: 7.0.7(vue@3.2.47)
       '@storybook/vue3-vite':
-        specifier: 7.0.2
-        version: 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1)(vue@3.2.47)
+        specifier: 7.0.7
+        version: 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4)(vue@3.2.47)
       '@testing-library/jest-dom':
         specifier: 5.16.5
         version: 5.16.5
@@ -824,8 +872,8 @@ importers:
         specifier: 0.0.1
         version: 0.0.1
       '@types/estree':
-        specifier: 1.0.0
-        version: 1.0.0
+        specifier: 1.0.1
+        version: 1.0.1
       '@types/gulp':
         specifier: 4.0.10
         version: 4.0.10
@@ -836,11 +884,11 @@ importers:
         specifier: 0.18.2
         version: 0.18.2
       '@types/micromatch':
-        specifier: 3.1.1
-        version: 3.1.1
+        specifier: 4.0.2
+        version: 4.0.2
       '@types/node':
-        specifier: 18.15.11
-        version: 18.15.11
+        specifier: 18.16.3
+        version: 18.16.3
       '@types/punycode':
         specifier: 2.1.0
         version: 2.1.0
@@ -869,14 +917,14 @@ importers:
         specifier: 8.5.4
         version: 8.5.4
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.57.1
-        version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4)
       '@typescript-eslint/parser':
-        specifier: 5.57.1
-        version: 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       '@vitest/coverage-c8':
-        specifier: ^0.29.8
-        version: 0.29.8(vitest@0.29.8)
+        specifier: 0.30.1
+        version: 0.30.1(vitest@0.30.1)
       '@vue/runtime-core':
         specifier: 3.2.47
         version: 3.2.47
@@ -886,42 +934,39 @@ importers:
       chokidar-cli:
         specifier: 3.0.0
         version: 3.0.0
-      chromatic:
-        specifier: 6.17.3
-        version: 6.17.3
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 12.9.0
-        version: 12.9.0
+        specifier: 12.11.0
+        version: 12.11.0
       eslint:
-        specifier: 8.37.0
-        version: 8.37.0
+        specifier: 8.39.0
+        version: 8.39.0
       eslint-plugin-import:
         specifier: 2.27.5
-        version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)
+        version: 2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)
       eslint-plugin-vue:
-        specifier: 9.10.0
-        version: 9.10.0(eslint@8.37.0)
+        specifier: 9.11.0
+        version: 9.11.0(eslint@8.39.0)
       fast-glob:
         specifier: 3.2.12
         version: 3.2.12
       happy-dom:
-        specifier: 8.9.0
-        version: 8.9.0
+        specifier: 9.10.2
+        version: 9.10.2
       micromatch:
         specifier: 3.1.10
         version: 3.1.10
       msw:
         specifier: 1.2.1
-        version: 1.2.1(typescript@5.0.3)
+        version: 1.2.1(typescript@5.0.4)
       msw-storybook-addon:
         specifier: 1.8.0
         version: 1.8.0(msw@1.2.1)
       prettier:
-        specifier: 2.8.7
-        version: 2.8.7
+        specifier: 2.8.8
+        version: 2.8.8
       react:
         specifier: 18.2.0
         version: 18.2.0
@@ -932,69 +977,69 @@ importers:
         specifier: 2.0.0
         version: 2.0.0
       storybook:
-        specifier: 7.0.2
-        version: 7.0.2
+        specifier: 7.0.7
+        version: 7.0.7
       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.0.2)(@storybook/components@7.0.2)(@storybook/core-events@7.0.2)(@storybook/manager-api@7.0.2)(@storybook/preview-api@7.0.2)(@storybook/theming@7.0.2)(@storybook/types@7.0.2)(react-dom@18.2.0)(react@18.2.0)
+        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.7)(@storybook/components@7.0.7)(@storybook/core-events@7.0.7)(@storybook/manager-api@7.0.7)(@storybook/preview-api@7.0.7)(@storybook/theming@7.0.7)(@storybook/types@7.0.7)(react-dom@18.2.0)(react@18.2.0)
       summaly:
         specifier: github:misskey-dev/summaly
-        version: github.com/misskey-dev/summaly/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992
+        version: github.com/misskey-dev/summaly/2d63e2a0066f89871e777cc81d43c1ade8c97517
       vite-plugin-turbosnap:
-        specifier: ^1.0.1
-        version: 1.0.1
+        specifier: 1.0.2
+        version: 1.0.2
       vitest:
-        specifier: 0.29.8
-        version: 0.29.8(happy-dom@8.9.0)(sass@1.60.0)
+        specifier: 0.30.1
+        version: 0.30.1(happy-dom@9.10.2)(sass@1.62.1)
       vitest-fetch-mock:
         specifier: 0.2.2
-        version: 0.2.2(vitest@0.29.8)
+        version: 0.2.2(vitest@0.30.1)
       vue-eslint-parser:
         specifier: 9.1.1
-        version: 9.1.1(eslint@8.37.0)
+        version: 9.1.1(eslint@8.39.0)
       vue-tsc:
-        specifier: 1.2.0
-        version: 1.2.0(typescript@5.0.3)
+        specifier: 1.6.3
+        version: 1.6.3(typescript@5.0.4)
 
   packages/misskey-js:
     dependencies:
       '@swc/cli':
         specifier: 0.1.62
-        version: 0.1.62(@swc/core@1.3.46)(chokidar@3.5.3)
+        version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3)
       '@swc/core':
-        specifier: 1.3.46
-        version: 1.3.46(@swc/helpers@0.5.0)
+        specifier: 1.3.56
+        version: 1.3.56
       eventemitter3:
-        specifier: 5.0.0
-        version: 5.0.0
+        specifier: 5.0.1
+        version: 5.0.1
       reconnecting-websocket:
         specifier: 4.4.0
         version: 4.4.0
     devDependencies:
       '@microsoft/api-extractor':
-        specifier: 7.34.4
-        version: 7.34.4(@types/node@18.15.11)
+        specifier: 7.34.7
+        version: 7.34.7(@types/node@18.16.3)
       '@swc/jest':
-        specifier: 0.2.24
-        version: 0.2.24(@swc/core@1.3.46)
+        specifier: 0.2.26
+        version: 0.2.26(@swc/core@1.3.56)
       '@types/jest':
-        specifier: 29.5.0
-        version: 29.5.0
+        specifier: 29.5.1
+        version: 29.5.1
       '@types/node':
-        specifier: 18.15.11
-        version: 18.15.11
+        specifier: 18.16.3
+        version: 18.16.3
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.57.1
-        version: 5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4)
       '@typescript-eslint/parser':
-        specifier: 5.57.1
-        version: 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       eslint:
-        specifier: 8.37.0
-        version: 8.37.0
+        specifier: 8.39.0
+        version: 8.39.0
       jest:
         specifier: 29.5.0
-        version: 29.5.0(@types/node@18.15.11)
+        version: 29.5.0(@types/node@18.16.3)
       jest-fetch-mock:
         specifier: 3.0.3
         version: 3.0.3
@@ -1008,14 +1053,14 @@ importers:
         specifier: 0.28.1
         version: 0.28.1
       typescript:
-        specifier: 5.0.3
-        version: 5.0.3
+        specifier: 5.0.4
+        version: 5.0.4
 
   packages/sw:
     dependencies:
       esbuild:
-        specifier: 0.17.15
-        version: 0.17.15
+        specifier: 0.17.18
+        version: 0.17.18
       idb-keyval:
         specifier: 6.2.0
         version: 6.2.0
@@ -1024,20 +1069,20 @@ importers:
         version: link:../misskey-js
     devDependencies:
       '@typescript-eslint/parser':
-        specifier: 5.57.1
-        version: 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+        specifier: 5.59.2
+        version: 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       '@typescript/lib-webworker':
         specifier: npm:@types/serviceworker@0.0.67
         version: /@types/serviceworker@0.0.67
       eslint:
-        specifier: 8.37.0
-        version: 8.37.0
+        specifier: 8.39.0
+        version: 8.39.0
       eslint-plugin-import:
         specifier: 2.27.5
-        version: 2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)
+        version: 2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)
       typescript:
-        specifier: 5.0.3
-        version: 5.0.3
+        specifier: 5.0.4
+        version: 5.0.4
 
 packages:
 
@@ -1057,7 +1102,7 @@ packages:
     resolution: {integrity: sha512-xguP2WR2Dv0gQ7Ykbdb7BNCnPnIPB94uTi0Z2NvkRBEnhbwjOQ7QyQKJXrVQg4qDpiD9hA5l5cCwy/z2OXgc3w==}
     dependencies:
       grapheme-splitter: 1.0.4
-    dev: true
+    dev: false
 
   /@aw-web-design/x-default-browser@1.4.88:
     resolution: {integrity: sha512-AkEmF0wcwYC2QkhK703Y83fxWARttIWXDmQN8+cof8FmFZ5BRhnNXGymeb1S73bOCLfWjYELxtujL56idCN/XA==}
@@ -1070,7 +1115,7 @@ packages:
     resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==}
     dependencies:
       '@aws-crypto/util': 3.0.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 1.14.1
     dev: false
 
@@ -1078,7 +1123,7 @@ packages:
     resolution: {integrity: sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==}
     dependencies:
       '@aws-crypto/util': 3.0.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 1.14.1
     dev: false
 
@@ -1094,7 +1139,7 @@ packages:
       '@aws-crypto/ie11-detection': 3.0.0
       '@aws-crypto/supports-web-crypto': 3.0.0
       '@aws-crypto/util': 3.0.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-locate-window': 3.208.0
       '@aws-sdk/util-utf8-browser': 3.259.0
       tslib: 1.14.1
@@ -1107,7 +1152,7 @@ packages:
       '@aws-crypto/sha256-js': 3.0.0
       '@aws-crypto/supports-web-crypto': 3.0.0
       '@aws-crypto/util': 3.0.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-locate-window': 3.208.0
       '@aws-sdk/util-utf8-browser': 3.259.0
       tslib: 1.14.1
@@ -1117,7 +1162,7 @@ packages:
     resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==}
     dependencies:
       '@aws-crypto/util': 3.0.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 1.14.1
     dev: false
 
@@ -1130,81 +1175,81 @@ packages:
   /@aws-crypto/util@3.0.0:
     resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-utf8-browser': 3.259.0
       tslib: 1.14.1
     dev: false
 
-  /@aws-sdk/abort-controller@3.306.0:
-    resolution: {integrity: sha512-ewCvdUrMJMlnkNaqXdG7L2H6O7CDI036y6lkTU8gQqa2lCzZvqBkzz6R5NbWqb8TJPi69Z7lXEITgk2b0+pl6w==}
+  /@aws-sdk/abort-controller@3.310.0:
+    resolution: {integrity: sha512-v1zrRQxDLA1MdPim159Vx/CPHqsB4uybSxRi1CnfHO5ZjHryx3a5htW2gdGAykVCul40+yJXvfpufMrELVxH+g==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/chunked-blob-reader@3.303.0:
-    resolution: {integrity: sha512-Cofcujz08TTKA7CtsIcWyFTyfe84KBa72kTjsXA9GQgn0cuUHZgGoFpBzoyDT8Ff3TPMKmAGDXmllRGFZw4mQw==}
+  /@aws-sdk/chunked-blob-reader@3.310.0:
+    resolution: {integrity: sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/client-s3@3.306.0:
-    resolution: {integrity: sha512-UkR5jFomspBAXSeAX/1T+YdUMusjg6xDHNvngL9JcW6qbtVv2zDuaGQ65474n2eFr4iJfgVL+IhKy+GNTZ03HA==}
+  /@aws-sdk/client-s3@3.321.1:
+    resolution: {integrity: sha512-SndPRdeofP2j1kPDLoPbJL8DzzjSciFb1S+Tda3UljOy9gQl68OAruwKloXHJE8GRkLJnYowlwLu36H1MvADJg==}
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-crypto/sha1-browser': 3.0.0
       '@aws-crypto/sha256-browser': 3.0.0
       '@aws-crypto/sha256-js': 3.0.0
-      '@aws-sdk/client-sts': 3.306.0
-      '@aws-sdk/config-resolver': 3.306.0
-      '@aws-sdk/credential-provider-node': 3.306.0
-      '@aws-sdk/eventstream-serde-browser': 3.306.0
-      '@aws-sdk/eventstream-serde-config-resolver': 3.306.0
-      '@aws-sdk/eventstream-serde-node': 3.306.0
-      '@aws-sdk/fetch-http-handler': 3.306.0
-      '@aws-sdk/hash-blob-browser': 3.306.0
-      '@aws-sdk/hash-node': 3.306.0
-      '@aws-sdk/hash-stream-node': 3.306.0
-      '@aws-sdk/invalid-dependency': 3.306.0
-      '@aws-sdk/md5-js': 3.306.0
-      '@aws-sdk/middleware-bucket-endpoint': 3.306.0
-      '@aws-sdk/middleware-content-length': 3.306.0
-      '@aws-sdk/middleware-endpoint': 3.306.0
-      '@aws-sdk/middleware-expect-continue': 3.306.0
-      '@aws-sdk/middleware-flexible-checksums': 3.306.0
-      '@aws-sdk/middleware-host-header': 3.306.0
-      '@aws-sdk/middleware-location-constraint': 3.306.0
-      '@aws-sdk/middleware-logger': 3.306.0
-      '@aws-sdk/middleware-recursion-detection': 3.306.0
-      '@aws-sdk/middleware-retry': 3.306.0
-      '@aws-sdk/middleware-sdk-s3': 3.306.0
-      '@aws-sdk/middleware-serde': 3.306.0
-      '@aws-sdk/middleware-signing': 3.306.0
-      '@aws-sdk/middleware-ssec': 3.306.0
-      '@aws-sdk/middleware-stack': 3.306.0
-      '@aws-sdk/middleware-user-agent': 3.306.0
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/node-http-handler': 3.306.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/signature-v4-multi-region': 3.306.0
-      '@aws-sdk/smithy-client': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/url-parser': 3.306.0
-      '@aws-sdk/util-base64': 3.303.0
-      '@aws-sdk/util-body-length-browser': 3.303.0
-      '@aws-sdk/util-body-length-node': 3.303.0
-      '@aws-sdk/util-defaults-mode-browser': 3.306.0
-      '@aws-sdk/util-defaults-mode-node': 3.306.0
-      '@aws-sdk/util-endpoints': 3.306.0
-      '@aws-sdk/util-retry': 3.306.0
-      '@aws-sdk/util-stream-browser': 3.306.0
-      '@aws-sdk/util-stream-node': 3.306.0
-      '@aws-sdk/util-user-agent-browser': 3.306.0
-      '@aws-sdk/util-user-agent-node': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
-      '@aws-sdk/util-waiter': 3.306.0
-      '@aws-sdk/xml-builder': 3.303.0
+      '@aws-sdk/client-sts': 3.321.1
+      '@aws-sdk/config-resolver': 3.310.0
+      '@aws-sdk/credential-provider-node': 3.321.1
+      '@aws-sdk/eventstream-serde-browser': 3.310.0
+      '@aws-sdk/eventstream-serde-config-resolver': 3.310.0
+      '@aws-sdk/eventstream-serde-node': 3.310.0
+      '@aws-sdk/fetch-http-handler': 3.310.0
+      '@aws-sdk/hash-blob-browser': 3.310.0
+      '@aws-sdk/hash-node': 3.310.0
+      '@aws-sdk/hash-stream-node': 3.310.0
+      '@aws-sdk/invalid-dependency': 3.310.0
+      '@aws-sdk/md5-js': 3.310.0
+      '@aws-sdk/middleware-bucket-endpoint': 3.310.0
+      '@aws-sdk/middleware-content-length': 3.310.0
+      '@aws-sdk/middleware-endpoint': 3.310.0
+      '@aws-sdk/middleware-expect-continue': 3.310.0
+      '@aws-sdk/middleware-flexible-checksums': 3.310.0
+      '@aws-sdk/middleware-host-header': 3.310.0
+      '@aws-sdk/middleware-location-constraint': 3.310.0
+      '@aws-sdk/middleware-logger': 3.310.0
+      '@aws-sdk/middleware-recursion-detection': 3.310.0
+      '@aws-sdk/middleware-retry': 3.310.0
+      '@aws-sdk/middleware-sdk-s3': 3.310.0
+      '@aws-sdk/middleware-serde': 3.310.0
+      '@aws-sdk/middleware-signing': 3.310.0
+      '@aws-sdk/middleware-ssec': 3.310.0
+      '@aws-sdk/middleware-stack': 3.310.0
+      '@aws-sdk/middleware-user-agent': 3.319.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/node-http-handler': 3.321.1
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/signature-v4-multi-region': 3.310.0
+      '@aws-sdk/smithy-client': 3.316.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/url-parser': 3.310.0
+      '@aws-sdk/util-base64': 3.310.0
+      '@aws-sdk/util-body-length-browser': 3.310.0
+      '@aws-sdk/util-body-length-node': 3.310.0
+      '@aws-sdk/util-defaults-mode-browser': 3.316.0
+      '@aws-sdk/util-defaults-mode-node': 3.316.0
+      '@aws-sdk/util-endpoints': 3.319.0
+      '@aws-sdk/util-retry': 3.310.0
+      '@aws-sdk/util-stream-browser': 3.310.0
+      '@aws-sdk/util-stream-node': 3.321.1
+      '@aws-sdk/util-user-agent-browser': 3.310.0
+      '@aws-sdk/util-user-agent-node': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
+      '@aws-sdk/util-waiter': 3.310.0
+      '@aws-sdk/xml-builder': 3.310.0
       fast-xml-parser: 4.1.2
       tslib: 2.5.0
     transitivePeerDependencies:
@@ -1212,581 +1257,581 @@ packages:
       - aws-crt
     dev: false
 
-  /@aws-sdk/client-sso-oidc@3.306.0:
-    resolution: {integrity: sha512-O27yrApCkbC0/uPRb1aHkENpFSqrkPbXRi76NF/8T97qC8bngRpy6yeafcQRrp9NGQSF/m9xbPWYsQuiurqedw==}
+  /@aws-sdk/client-sso-oidc@3.321.1:
+    resolution: {integrity: sha512-PBVfHQbyrsfzbnO6u9d9Sik8JlXGLhHj3zLd87iBkYXBdHwD5NuvwWu7OtjUtrHjP4SfzodVwfjmTbDAFqbtzw==}
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-crypto/sha256-browser': 3.0.0
       '@aws-crypto/sha256-js': 3.0.0
-      '@aws-sdk/config-resolver': 3.306.0
-      '@aws-sdk/fetch-http-handler': 3.306.0
-      '@aws-sdk/hash-node': 3.306.0
-      '@aws-sdk/invalid-dependency': 3.306.0
-      '@aws-sdk/middleware-content-length': 3.306.0
-      '@aws-sdk/middleware-endpoint': 3.306.0
-      '@aws-sdk/middleware-host-header': 3.306.0
-      '@aws-sdk/middleware-logger': 3.306.0
-      '@aws-sdk/middleware-recursion-detection': 3.306.0
-      '@aws-sdk/middleware-retry': 3.306.0
-      '@aws-sdk/middleware-serde': 3.306.0
-      '@aws-sdk/middleware-stack': 3.306.0
-      '@aws-sdk/middleware-user-agent': 3.306.0
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/node-http-handler': 3.306.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/smithy-client': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/url-parser': 3.306.0
-      '@aws-sdk/util-base64': 3.303.0
-      '@aws-sdk/util-body-length-browser': 3.303.0
-      '@aws-sdk/util-body-length-node': 3.303.0
-      '@aws-sdk/util-defaults-mode-browser': 3.306.0
-      '@aws-sdk/util-defaults-mode-node': 3.306.0
-      '@aws-sdk/util-endpoints': 3.306.0
-      '@aws-sdk/util-retry': 3.306.0
-      '@aws-sdk/util-user-agent-browser': 3.306.0
-      '@aws-sdk/util-user-agent-node': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/config-resolver': 3.310.0
+      '@aws-sdk/fetch-http-handler': 3.310.0
+      '@aws-sdk/hash-node': 3.310.0
+      '@aws-sdk/invalid-dependency': 3.310.0
+      '@aws-sdk/middleware-content-length': 3.310.0
+      '@aws-sdk/middleware-endpoint': 3.310.0
+      '@aws-sdk/middleware-host-header': 3.310.0
+      '@aws-sdk/middleware-logger': 3.310.0
+      '@aws-sdk/middleware-recursion-detection': 3.310.0
+      '@aws-sdk/middleware-retry': 3.310.0
+      '@aws-sdk/middleware-serde': 3.310.0
+      '@aws-sdk/middleware-stack': 3.310.0
+      '@aws-sdk/middleware-user-agent': 3.319.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/node-http-handler': 3.321.1
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/smithy-client': 3.316.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/url-parser': 3.310.0
+      '@aws-sdk/util-base64': 3.310.0
+      '@aws-sdk/util-body-length-browser': 3.310.0
+      '@aws-sdk/util-body-length-node': 3.310.0
+      '@aws-sdk/util-defaults-mode-browser': 3.316.0
+      '@aws-sdk/util-defaults-mode-node': 3.316.0
+      '@aws-sdk/util-endpoints': 3.319.0
+      '@aws-sdk/util-retry': 3.310.0
+      '@aws-sdk/util-user-agent-browser': 3.310.0
+      '@aws-sdk/util-user-agent-node': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/client-sso@3.306.0:
-    resolution: {integrity: sha512-uqfLUOP9LlBoqXe3P250TPX3fGrabfRt9Q9rlLFK0fVBI7HPIQ/wsPplLoPrMeT04qQmTI03UnVKMNza3GqyIg==}
+  /@aws-sdk/client-sso@3.321.1:
+    resolution: {integrity: sha512-ecoT4tBGtRJR5G7oLBTMXZmgZZlff1amhSdKPEtkWxv6kWc8VPb5rRuRgVPsDR9HuesI6ZVlODptvGtnfkIJwA==}
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-crypto/sha256-browser': 3.0.0
       '@aws-crypto/sha256-js': 3.0.0
-      '@aws-sdk/config-resolver': 3.306.0
-      '@aws-sdk/fetch-http-handler': 3.306.0
-      '@aws-sdk/hash-node': 3.306.0
-      '@aws-sdk/invalid-dependency': 3.306.0
-      '@aws-sdk/middleware-content-length': 3.306.0
-      '@aws-sdk/middleware-endpoint': 3.306.0
-      '@aws-sdk/middleware-host-header': 3.306.0
-      '@aws-sdk/middleware-logger': 3.306.0
-      '@aws-sdk/middleware-recursion-detection': 3.306.0
-      '@aws-sdk/middleware-retry': 3.306.0
-      '@aws-sdk/middleware-serde': 3.306.0
-      '@aws-sdk/middleware-stack': 3.306.0
-      '@aws-sdk/middleware-user-agent': 3.306.0
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/node-http-handler': 3.306.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/smithy-client': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/url-parser': 3.306.0
-      '@aws-sdk/util-base64': 3.303.0
-      '@aws-sdk/util-body-length-browser': 3.303.0
-      '@aws-sdk/util-body-length-node': 3.303.0
-      '@aws-sdk/util-defaults-mode-browser': 3.306.0
-      '@aws-sdk/util-defaults-mode-node': 3.306.0
-      '@aws-sdk/util-endpoints': 3.306.0
-      '@aws-sdk/util-retry': 3.306.0
-      '@aws-sdk/util-user-agent-browser': 3.306.0
-      '@aws-sdk/util-user-agent-node': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/config-resolver': 3.310.0
+      '@aws-sdk/fetch-http-handler': 3.310.0
+      '@aws-sdk/hash-node': 3.310.0
+      '@aws-sdk/invalid-dependency': 3.310.0
+      '@aws-sdk/middleware-content-length': 3.310.0
+      '@aws-sdk/middleware-endpoint': 3.310.0
+      '@aws-sdk/middleware-host-header': 3.310.0
+      '@aws-sdk/middleware-logger': 3.310.0
+      '@aws-sdk/middleware-recursion-detection': 3.310.0
+      '@aws-sdk/middleware-retry': 3.310.0
+      '@aws-sdk/middleware-serde': 3.310.0
+      '@aws-sdk/middleware-stack': 3.310.0
+      '@aws-sdk/middleware-user-agent': 3.319.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/node-http-handler': 3.321.1
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/smithy-client': 3.316.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/url-parser': 3.310.0
+      '@aws-sdk/util-base64': 3.310.0
+      '@aws-sdk/util-body-length-browser': 3.310.0
+      '@aws-sdk/util-body-length-node': 3.310.0
+      '@aws-sdk/util-defaults-mode-browser': 3.316.0
+      '@aws-sdk/util-defaults-mode-node': 3.316.0
+      '@aws-sdk/util-endpoints': 3.319.0
+      '@aws-sdk/util-retry': 3.310.0
+      '@aws-sdk/util-user-agent-browser': 3.310.0
+      '@aws-sdk/util-user-agent-node': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/client-sts@3.306.0:
-    resolution: {integrity: sha512-LivDrH0OnAZDC3EB6hVrrl25itlMLn/C/epwDjpnH2Qdq+gjbZ0ElVNu8XOX4qaXoo0zyV5pztnzwD/A76mX2g==}
+  /@aws-sdk/client-sts@3.321.1:
+    resolution: {integrity: sha512-AB+N4a1TVEKl9Sd5O2TxTprEZp7Va6zPZLMraFAYMdmJVBmCmmwyBs7ygju685DpQ1dos5PRsKCRcossyY5pDQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-crypto/sha256-browser': 3.0.0
       '@aws-crypto/sha256-js': 3.0.0
-      '@aws-sdk/config-resolver': 3.306.0
-      '@aws-sdk/credential-provider-node': 3.306.0
-      '@aws-sdk/fetch-http-handler': 3.306.0
-      '@aws-sdk/hash-node': 3.306.0
-      '@aws-sdk/invalid-dependency': 3.306.0
-      '@aws-sdk/middleware-content-length': 3.306.0
-      '@aws-sdk/middleware-endpoint': 3.306.0
-      '@aws-sdk/middleware-host-header': 3.306.0
-      '@aws-sdk/middleware-logger': 3.306.0
-      '@aws-sdk/middleware-recursion-detection': 3.306.0
-      '@aws-sdk/middleware-retry': 3.306.0
-      '@aws-sdk/middleware-sdk-sts': 3.306.0
-      '@aws-sdk/middleware-serde': 3.306.0
-      '@aws-sdk/middleware-signing': 3.306.0
-      '@aws-sdk/middleware-stack': 3.306.0
-      '@aws-sdk/middleware-user-agent': 3.306.0
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/node-http-handler': 3.306.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/smithy-client': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/url-parser': 3.306.0
-      '@aws-sdk/util-base64': 3.303.0
-      '@aws-sdk/util-body-length-browser': 3.303.0
-      '@aws-sdk/util-body-length-node': 3.303.0
-      '@aws-sdk/util-defaults-mode-browser': 3.306.0
-      '@aws-sdk/util-defaults-mode-node': 3.306.0
-      '@aws-sdk/util-endpoints': 3.306.0
-      '@aws-sdk/util-retry': 3.306.0
-      '@aws-sdk/util-user-agent-browser': 3.306.0
-      '@aws-sdk/util-user-agent-node': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/config-resolver': 3.310.0
+      '@aws-sdk/credential-provider-node': 3.321.1
+      '@aws-sdk/fetch-http-handler': 3.310.0
+      '@aws-sdk/hash-node': 3.310.0
+      '@aws-sdk/invalid-dependency': 3.310.0
+      '@aws-sdk/middleware-content-length': 3.310.0
+      '@aws-sdk/middleware-endpoint': 3.310.0
+      '@aws-sdk/middleware-host-header': 3.310.0
+      '@aws-sdk/middleware-logger': 3.310.0
+      '@aws-sdk/middleware-recursion-detection': 3.310.0
+      '@aws-sdk/middleware-retry': 3.310.0
+      '@aws-sdk/middleware-sdk-sts': 3.310.0
+      '@aws-sdk/middleware-serde': 3.310.0
+      '@aws-sdk/middleware-signing': 3.310.0
+      '@aws-sdk/middleware-stack': 3.310.0
+      '@aws-sdk/middleware-user-agent': 3.319.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/node-http-handler': 3.321.1
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/smithy-client': 3.316.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/url-parser': 3.310.0
+      '@aws-sdk/util-base64': 3.310.0
+      '@aws-sdk/util-body-length-browser': 3.310.0
+      '@aws-sdk/util-body-length-node': 3.310.0
+      '@aws-sdk/util-defaults-mode-browser': 3.316.0
+      '@aws-sdk/util-defaults-mode-node': 3.316.0
+      '@aws-sdk/util-endpoints': 3.319.0
+      '@aws-sdk/util-retry': 3.310.0
+      '@aws-sdk/util-user-agent-browser': 3.310.0
+      '@aws-sdk/util-user-agent-node': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       fast-xml-parser: 4.1.2
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/config-resolver@3.306.0:
-    resolution: {integrity: sha512-kpqHu6LvNMYxullm+tLCsY6KQ2mZUxZTdyWJKTYLZCTxj4HcGJxf4Jxj9dwFAZVl/clcVPGWcHJaQJjyjwzBzw==}
+  /@aws-sdk/config-resolver@3.310.0:
+    resolution: {integrity: sha512-8vsT+/50lOqfDxka9m/rRt6oxv1WuGZoP8oPMk0Dt+TxXMbAzf4+rejBgiB96wshI1k3gLokYRjSQZn+dDtT8g==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-config-provider': 3.295.0
-      '@aws-sdk/util-middleware': 3.306.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-config-provider': 3.310.0
+      '@aws-sdk/util-middleware': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/credential-provider-env@3.306.0:
-    resolution: {integrity: sha512-DTH+aMvMu+LAoWW+yfPkWzFXt/CPNFQ7+/4xiMnc7FWf+tjt+HZIrPECAV2rBVppNCkh7PC+xDSN61PFvBYOsw==}
+  /@aws-sdk/credential-provider-env@3.310.0:
+    resolution: {integrity: sha512-vvIPQpI16fj95xwS7M3D48F7QhZJBnnCgB5lR+b7So+vsG9ibm1mZRVGzVpdxCvgyOhHFbvrby9aalNJmmIP1A==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/credential-provider-imds@3.306.0:
-    resolution: {integrity: sha512-WdrNhq2MwvjZk2I8Of+bZ/qWHG2hREQpwlBiG3tMeEkuywx7M1x3Rt0eHgiR1sTcm05kxNn0rB4OeWOeek37cA==}
+  /@aws-sdk/credential-provider-imds@3.310.0:
+    resolution: {integrity: sha512-baxK7Zp6dai5AGW01FIW27xS2KAaPUmKLIXv5SvFYsUgXXvNW55im4uG3b+2gA0F7V+hXvVBH08OEqmwW6we5w==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/url-parser': 3.306.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/url-parser': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/credential-provider-ini@3.306.0:
-    resolution: {integrity: sha512-6VvP0YmXVd+pCnlD2iTDhNvO2Ikzyk9Ade/t5R1eZ4Vf1gKhDiNA2/AgDt9XlzQHk7iw1okTmYCeQsK1j+7+NQ==}
+  /@aws-sdk/credential-provider-ini@3.321.1:
+    resolution: {integrity: sha512-prndSVQhiikNaI40bYnM2Q8PkC35FCwhbQnBk6KXNvdtfo9RqatMC639F+6oryb3BuMy++Ij4Yoi8WnPBs5Sww==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/credential-provider-env': 3.306.0
-      '@aws-sdk/credential-provider-imds': 3.306.0
-      '@aws-sdk/credential-provider-process': 3.306.0
-      '@aws-sdk/credential-provider-sso': 3.306.0
-      '@aws-sdk/credential-provider-web-identity': 3.306.0
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/shared-ini-file-loader': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/credential-provider-env': 3.310.0
+      '@aws-sdk/credential-provider-imds': 3.310.0
+      '@aws-sdk/credential-provider-process': 3.310.0
+      '@aws-sdk/credential-provider-sso': 3.321.1
+      '@aws-sdk/credential-provider-web-identity': 3.310.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/shared-ini-file-loader': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/credential-provider-node@3.306.0:
-    resolution: {integrity: sha512-HYuMmABRzbVWo03CElRUa+T+yenyUmLkwNCVAAvIRmbr9TnLT/bJbplXpUSzgSCS6T3TgwbQ9zf9xY9tX+gHzA==}
+  /@aws-sdk/credential-provider-node@3.321.1:
+    resolution: {integrity: sha512-5B1waOwSvY2JMLGRebo7IUqnTaGoCnby9cRbG/dhi7Ke97M3V8380S9THDJ/bktjL8zHEVfBVZy7HhXHzhSjEg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/credential-provider-env': 3.306.0
-      '@aws-sdk/credential-provider-imds': 3.306.0
-      '@aws-sdk/credential-provider-ini': 3.306.0
-      '@aws-sdk/credential-provider-process': 3.306.0
-      '@aws-sdk/credential-provider-sso': 3.306.0
-      '@aws-sdk/credential-provider-web-identity': 3.306.0
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/shared-ini-file-loader': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/credential-provider-env': 3.310.0
+      '@aws-sdk/credential-provider-imds': 3.310.0
+      '@aws-sdk/credential-provider-ini': 3.321.1
+      '@aws-sdk/credential-provider-process': 3.310.0
+      '@aws-sdk/credential-provider-sso': 3.321.1
+      '@aws-sdk/credential-provider-web-identity': 3.310.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/shared-ini-file-loader': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/credential-provider-process@3.306.0:
-    resolution: {integrity: sha512-2RezGskHqJeHtGbK7CqhGNAoqXgQJb7FfPFqwUQ9oVDZS8f145jVwajjHcc7Qn3IwGoqylMF3uXIljUv89uDzA==}
+  /@aws-sdk/credential-provider-process@3.310.0:
+    resolution: {integrity: sha512-h73sg6GPMUWC+3zMCbA1nZ2O03nNJt7G96JdmnantiXBwHpRKWW8nBTLzx5uhXn6hTuTaoQRP/P+oxQJKYdMmA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/shared-ini-file-loader': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/shared-ini-file-loader': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/credential-provider-sso@3.306.0:
-    resolution: {integrity: sha512-6msBUisMdOzk0ywJQNunZIb0rVMaA6GTx7ek8aCuWInX+lJm0oEPPVp+b3ewwVheih1rRC2bgNk8eAjfC9YcKw==}
+  /@aws-sdk/credential-provider-sso@3.321.1:
+    resolution: {integrity: sha512-kg0rc1OacJFgAvmZj0TOu+BSc+yRdnC5dO/RAag3XU6+hlQI5/C080RQp9Qj6V7ga0HtAJMRwJcUlCPA3RJPug==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/client-sso': 3.306.0
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/shared-ini-file-loader': 3.306.0
-      '@aws-sdk/token-providers': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/client-sso': 3.321.1
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/shared-ini-file-loader': 3.310.0
+      '@aws-sdk/token-providers': 3.321.1
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/credential-provider-web-identity@3.306.0:
-    resolution: {integrity: sha512-MOQGQaOtdo4zLQZ1bRjD2n1PUzfNty+sKe+1wlm5bIqTN93UX3S8f0QznucZr7uJxI4Z14ZLwuYeAUV4Tgchlw==}
+  /@aws-sdk/credential-provider-web-identity@3.310.0:
+    resolution: {integrity: sha512-H4SzuZXILNhK6/IR1uVvsUDZvzc051hem7GLyYghBCu8mU+tq28YhKE8MfSroi6eL2e5Vujloij1OM2EQQkPkw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/eventstream-codec@3.306.0:
-    resolution: {integrity: sha512-tQCo0tl/NdD3rVt9hDkGO3SLPXdohlRx6j9IMor5HpM7tCFcB4WNpDHPHyClxRoprLT04BZksJZcbVUybAeTcA==}
+  /@aws-sdk/eventstream-codec@3.310.0:
+    resolution: {integrity: sha512-clIeSgWbZbxwtsxZ/yoedNM0/kJFSIjjHPikuDGhxhqc+vP6TN3oYyVMFrYwFaTFhk2+S5wZcWYMw8Op1pWo+A==}
     dependencies:
       '@aws-crypto/crc32': 3.0.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-hex-encoding': 3.295.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-hex-encoding': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/eventstream-serde-browser@3.306.0:
-    resolution: {integrity: sha512-Vbx0KQzZ5YAeYwC3tbCGsJZ2nSqKQlQFbuwhXe/Btz/dh1HaJPMxoIC8Dr0b6Z7S8l2Pq4ucqdNaUzLeJJbezw==}
+  /@aws-sdk/eventstream-serde-browser@3.310.0:
+    resolution: {integrity: sha512-3S6ziuQVALgEyz0TANGtYDVeG8ArK4Y05mcgrs8qUTmsvlDIXX37cR/DvmVbNB76M4IrsZeSAIajL9644CywkA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/eventstream-serde-universal': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/eventstream-serde-universal': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/eventstream-serde-config-resolver@3.306.0:
-    resolution: {integrity: sha512-MBNJspHTbP3+bZeDgfH+FcAxBodKH3NnXmSCyUosMJMaNQ8JtqsaW+y6FfvC6aVht3FyC6qKWBjuKfEeG2jLCA==}
+  /@aws-sdk/eventstream-serde-config-resolver@3.310.0:
+    resolution: {integrity: sha512-8s1Qdn9STj+sV75nUp9yt0W6fHS4BZ2jTm4Z/1Pcbvh2Gqs0WjH5n2StS+pDW5Y9J/HSGBl0ogmUr5lC5bXFHg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/eventstream-serde-node@3.306.0:
-    resolution: {integrity: sha512-Fq6n8vUMxMSqWFD2xSjh/X7Wyr+CggGHjsVgIFfXIXEK14iTOnOK7/yS86mDR6GBRupIfrw/Q+Jf0A7b8o2Wlg==}
+  /@aws-sdk/eventstream-serde-node@3.310.0:
+    resolution: {integrity: sha512-kSnRomCgW43K9TmQYuwN9+AoYPnhyOKroanUMyZEzJk7rpCPMj4OzaUpXfDYOvznFNYn7NLaH6nHLJAr0VPlJA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/eventstream-serde-universal': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/eventstream-serde-universal': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/eventstream-serde-universal@3.306.0:
-    resolution: {integrity: sha512-Xp2JCRkZm3dyVv2O/EJnOP1vwUwEmXXminbX1MgnuVIMifSbbjeo6lGqCsjrIkXyymkx4TvrmPmSWlwzPCnyVw==}
+  /@aws-sdk/eventstream-serde-universal@3.310.0:
+    resolution: {integrity: sha512-Qyjt5k/waV5cDukpgT824ISZAz5U0pwzLz5ztR409u85AGNkF/9n7MS+LSyBUBSb0WJ5pUeSD47WBk+nLq9Nhw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/eventstream-codec': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/eventstream-codec': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/fetch-http-handler@3.306.0:
-    resolution: {integrity: sha512-T8OODOnPpDqkXS+XSMIkd6hf90h833JLN93wq3ibbyD/WvGveufFFHsbsNyccE9+CSv/BjEuN5QbHqTKTp3BlA==}
+  /@aws-sdk/fetch-http-handler@3.310.0:
+    resolution: {integrity: sha512-Bi9vIwzdkw1zMcvi/zGzlWS9KfIEnAq4NNhsnCxbQ4OoIRU9wvU+WGZdBBhxg0ZxZmpp1j1aZhU53lLjA07MHw==}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/querystring-builder': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-base64': 3.303.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/querystring-builder': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-base64': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/hash-blob-browser@3.306.0:
-    resolution: {integrity: sha512-OK3xQ60Vz1VTDgMGk8PjFESQlDjXCLR/q1xc9ODyXBO2/A+ntUxMBcGfu7KYjtGI69g91H/BGhPAy+W5Gg+rmw==}
+  /@aws-sdk/hash-blob-browser@3.310.0:
+    resolution: {integrity: sha512-OoR8p0cbypToysLT0v3o2oyjy6+DKrY7GNCAzHOHJK9xmqXCt+DsjKoPeiY7o1sWX2aN6Plmvubj/zWxMKEn/A==}
     dependencies:
-      '@aws-sdk/chunked-blob-reader': 3.303.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/chunked-blob-reader': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/hash-node@3.306.0:
-    resolution: {integrity: sha512-EcSLd6gKoDEEBPZqEv+Ky9gIyefwyyrAJGILGKoYBmcOIY7Y0xKId0hxCa9/1xvWTaVC1u+rA06DGgksZOa78w==}
+  /@aws-sdk/hash-node@3.310.0:
+    resolution: {integrity: sha512-NvE2fhRc8GRwCXBfDehxVAWCmVwVMILliAKVPAEr4yz2CkYs0tqU51S48x23dtna07H4qHtgpeNqVTthcIQOEQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-buffer-from': 3.303.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-buffer-from': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/hash-stream-node@3.306.0:
-    resolution: {integrity: sha512-9owhr/kmCRwRZWAeGnf3k+L5tD/DcLAEMfbIuH8uolEigI+YGRMXnJB0PpyzIyd1N8TM/bm6WW3uvQiFHy7Hxw==}
+  /@aws-sdk/hash-stream-node@3.310.0:
+    resolution: {integrity: sha512-ZoXdybNgvMz1Hl6k/e32xVL3jmG5p2IEk5mTtLfFEuskTJ74Z+VMYKkkF1whyy7KQfH83H+TQGnsGtlRCchQKw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/invalid-dependency@3.306.0:
-    resolution: {integrity: sha512-9Mkcr+qG7QR4R5bJcA8bBNd8E2x6WaZStsQ3QeFbdQr3V3Tunvra/KlCFsEL55GgU8BZt5isOaHqq7uxs5ILtQ==}
+  /@aws-sdk/invalid-dependency@3.310.0:
+    resolution: {integrity: sha512-1s5RG5rSPXoa/aZ/Kqr5U/7lqpx+Ry81GprQ2bxWqJvWQIJ0IRUwo5pk8XFxbKVr/2a+4lZT/c3OGoBOM1yRRA==}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/is-array-buffer@3.303.0:
-    resolution: {integrity: sha512-IitBTr+pou7v5BrYLFH/SbIf3g1LIgMhcI3bDXBq2FjzmDftj4bW8BOmg05b9YKf2TrrggvJ4yk/jH+yYFXoJQ==}
+  /@aws-sdk/is-array-buffer@3.310.0:
+    resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/lib-storage@3.306.0(@aws-sdk/abort-controller@3.306.0)(@aws-sdk/client-s3@3.306.0):
-    resolution: {integrity: sha512-/XPTLZ39gxaQGpcZhYW+o/cKpkELt94Exurs/2YSjNv0BiTueDLoY8pPMkXeuspOKlcPLQneX2HnjbvaQXjUhQ==}
+  /@aws-sdk/lib-storage@3.321.1(@aws-sdk/abort-controller@3.310.0)(@aws-sdk/client-s3@3.321.1):
+    resolution: {integrity: sha512-a9JZRuq1sc64zgoIIMipVovDgdCuiCaTRB0pY4rjVIvhQVgdyg734rlZbvdDOduiKYz7QvYkqMAdsp0ASV14Gg==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       '@aws-sdk/abort-controller': ^3.0.0
       '@aws-sdk/client-s3': ^3.0.0
     dependencies:
-      '@aws-sdk/abort-controller': 3.306.0
-      '@aws-sdk/client-s3': 3.306.0
-      '@aws-sdk/middleware-endpoint': 3.306.0
-      '@aws-sdk/smithy-client': 3.306.0
+      '@aws-sdk/abort-controller': 3.310.0
+      '@aws-sdk/client-s3': 3.321.1
+      '@aws-sdk/middleware-endpoint': 3.310.0
+      '@aws-sdk/smithy-client': 3.316.0
       buffer: 5.6.0
       events: 3.3.0
       stream-browserify: 3.0.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/md5-js@3.306.0:
-    resolution: {integrity: sha512-QCv5hfFL3AsSQPZvO8YOaBoNlRu6+B2lVSVZHLaWrortlaRGQi5+fYgYPhpTho/2cw0qeogB4pQIho3UgCAmfg==}
+  /@aws-sdk/md5-js@3.310.0:
+    resolution: {integrity: sha512-x5sRBUrEfLWAS1EhwbbDQ7cXq6uvBxh3qR2XAsnGvFFceTeAadk7cVogWxlk3PC+OCeeym7c3/6Bv2HQ2f1YyQ==}
     dependencies:
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-bucket-endpoint@3.306.0:
-    resolution: {integrity: sha512-DL8Q146s0nJ7MSHxt8sr6MDR4tIh+q7z9Yki7o1soCMNGTaYCUC20nSffs7IhQAUo1/q3pRh8LoJNtIpa7Q3pA==}
+  /@aws-sdk/middleware-bucket-endpoint@3.310.0:
+    resolution: {integrity: sha512-uJJfHI7v4AgbJZRLtyI8ap2QRWkBokGc3iyUoQ+dVNT3/CE2ZCu694A6W+H0dRqg79dIE+f9CRNdtLGa/Ehhvg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-arn-parser': 3.295.0
-      '@aws-sdk/util-config-provider': 3.295.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-arn-parser': 3.310.0
+      '@aws-sdk/util-config-provider': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-content-length@3.306.0:
-    resolution: {integrity: sha512-JbONf2Ms+/DVRcpFNsKGdOQU94Js56KV+AhlPJmCwLxfyWvQjTt0KxFC1Dd+cjeNEXUduvBarrehgsqFlWnoHQ==}
+  /@aws-sdk/middleware-content-length@3.310.0:
+    resolution: {integrity: sha512-P8tQZxgDt6CAh1wd/W6WPzjc+uWPJwQkm+F7rAwRlM+k9q17HrhnksGDKcpuuLyIhPQYdmOMIkpKVgXGa4avhQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-endpoint@3.306.0:
-    resolution: {integrity: sha512-i3QRiwgkcsuVN55O7l8I/QGwCypGRZXdYkPjU56LI2w2oiZ82f/nVMNXVc+ZFm2YH7WbCE+5jguw2J7HXdOlyQ==}
+  /@aws-sdk/middleware-endpoint@3.310.0:
+    resolution: {integrity: sha512-Z+N2vOL8K354/lstkClxLLsr6hCpVRh+0tCMXrVj66/NtKysCEZ/0b9LmqOwD9pWHNiI2mJqXwY0gxNlKAroUg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/middleware-serde': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/url-parser': 3.306.0
-      '@aws-sdk/util-middleware': 3.306.0
+      '@aws-sdk/middleware-serde': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/url-parser': 3.310.0
+      '@aws-sdk/util-middleware': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-expect-continue@3.306.0:
-    resolution: {integrity: sha512-k+UgyQpTqFaizClQRdVTpcGJduRXKS0GCDDV/pdz1NVx09YHCIxVpkpdkYj+2T1Ufp3bK7ju2FLaXJwoO6/r+Q==}
+  /@aws-sdk/middleware-expect-continue@3.310.0:
+    resolution: {integrity: sha512-l3d1z2gt+gINJDnPSyu84IxfzjzPfCQrqC1sunw2cZGo/sXtEiq698Q3SiTcO2PGP4LBQAy2RHb5wVBJP708CQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-flexible-checksums@3.306.0:
-    resolution: {integrity: sha512-0Z3eKIVdP9son45Id18pDWwfcqoYTzDsSfePMD//HByBQQusL8pS1D1mlWqVpnV++ZfNmPcnph+v6cIfHYVM4Q==}
+  /@aws-sdk/middleware-flexible-checksums@3.310.0:
+    resolution: {integrity: sha512-5ndnLgzgGVpWkmHBAiYkagHqiSuow8q62J4J6E2PzaQ77+fm8W3nfdy7hK5trHokEyouCZdxT/XK/IRhgj/4PA==}
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-crypto/crc32': 3.0.0
       '@aws-crypto/crc32c': 3.0.0
-      '@aws-sdk/is-array-buffer': 3.303.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/is-array-buffer': 3.310.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-host-header@3.306.0:
-    resolution: {integrity: sha512-mHDHK9E+c7HwMlrCJ+VFSB6tkq8oJVkYEHCvPkdrnzN/g9P/d/UhPIeGapZXMbAIZEaLpEGqs536mYzeRKZG8A==}
+  /@aws-sdk/middleware-host-header@3.310.0:
+    resolution: {integrity: sha512-QWSA+46/hXorXyWa61ic2K7qZzwHTiwfk2e9mRRjeIRepUgI3qxFjsYqrWtrOGBjmFmq0pYIY8Bb/DCJuQqcoA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-location-constraint@3.306.0:
-    resolution: {integrity: sha512-TfSPLDcl/QTs2OHplJhbJMzdWe3/du5wrNn0LECligzX4f7Ygn2zV/UtqHet36tv206ssDm6b9g8KvRvG3FT2g==}
+  /@aws-sdk/middleware-location-constraint@3.310.0:
+    resolution: {integrity: sha512-LFm0JTQWwTPWL/tZU2wsQTl8J5PpDEkXjEhaXVKamtyH0xhysRqd+0n92n65dc8oztAuQkb9xUbErGn5b6gsew==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-logger@3.306.0:
-    resolution: {integrity: sha512-1FRHp/QB0Lb+CgP+c9CYW6BZh+q+5pnuOKo/Rd6hjYiM+kT1G/cWdXnMJQBR4rbTCTixbqCnObNJ1EyP/ofQhQ==}
+  /@aws-sdk/middleware-logger@3.310.0:
+    resolution: {integrity: sha512-Lurm8XofrASBRnAVtiSNuDSRsRqPNg27RIFLLsLp/pqog9nFJ0vz0kgdb9S5Z+zw83Mm+UlqOe6D8NTUNp4fVg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-recursion-detection@3.306.0:
-    resolution: {integrity: sha512-Hpj42ZLmwCy/CtVxi57NTeOEPoUJlivF3VIgowZ9JhaF61cakVKyrJ+f3jwXciDUtuYrdKm5Wf6prW6apWo0YA==}
+  /@aws-sdk/middleware-recursion-detection@3.310.0:
+    resolution: {integrity: sha512-SuB75/xk/gyue24gkriTwO2jFd7YcUGZDClQYuRejgbXSa3CO0lWyawQtfLcSSEBp9izrEVXuFH24K1eAft5nQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-retry@3.306.0:
-    resolution: {integrity: sha512-eMyfr/aeurXXDz4x+WVrvLI8fVDP6klJOjziBEWZ/MUNP/hTFhkiQsMVbvT6O4Pspp7+FgCSdcUPG6Os2gK+CQ==}
+  /@aws-sdk/middleware-retry@3.310.0:
+    resolution: {integrity: sha512-oTPsRy2W4s+dfxbJPW7Km+hHtv/OMsNsVfThAq8DDYKC13qlr1aAyOqGLD+dpBy2aKe7ss517Sy2HcHtHqm7/g==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/service-error-classification': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-middleware': 3.306.0
-      '@aws-sdk/util-retry': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/service-error-classification': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-middleware': 3.310.0
+      '@aws-sdk/util-retry': 3.310.0
       tslib: 2.5.0
       uuid: 8.3.2
     dev: false
 
-  /@aws-sdk/middleware-sdk-s3@3.306.0:
-    resolution: {integrity: sha512-x1V4NKpY8waDBTUjg2TLcqLZjD6OoED0NMFvKPxul/l2rzInvcNK6L2ifxxhYb7YG9N4MtLnCvAUqEW5h3eLYw==}
+  /@aws-sdk/middleware-sdk-s3@3.310.0:
+    resolution: {integrity: sha512-QK9x9g2ksg0hOjjYgqddeFcn5ctUEGdxJVu4OumPXceulefMcSO2jyH2qTybYSA93nqNQFdFmg5wQfvIRUWFCQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-arn-parser': 3.295.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-arn-parser': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-sdk-sts@3.306.0:
-    resolution: {integrity: sha512-2rSAR3nc5faYuEnh1KxQMCMCkEkJyaDfA3zwWLqZ+/TBCH0PlPkBv+Z9yXmteEki0vI5Hr+e+atTutJZoyG13g==}
+  /@aws-sdk/middleware-sdk-sts@3.310.0:
+    resolution: {integrity: sha512-+5PFwlYNLvLLIfw0ASAoWV/iIF8Zv6R6QGtyP0CclhRSvNjgbQDVnV0g95MC5qvh+GB/Yjlkt8qAjLSPjHfsrQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/middleware-signing': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/middleware-signing': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-serde@3.306.0:
-    resolution: {integrity: sha512-M3gyPLPduZXMvdgt4XEpVO+3t0ZVPdgeQQwG6JnXv0dgyUizshYs4lrVOAb1KwF6StsmkrAgSN+I273elLiKjA==}
+  /@aws-sdk/middleware-serde@3.310.0:
+    resolution: {integrity: sha512-RNeeTVWSLTaentUeCgQKZhAl+C6hxtwD78cQWS10UymWpQFwbaxztzKUu4UQS5xA2j6PxwPRRUjqa4jcFjfLsg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-signing@3.306.0:
-    resolution: {integrity: sha512-JhpSriN4xa4a/p5gAPL0OWFKJF4eWYU3K+LLlXBNGMbxg/qNL4skgT4dMFe3ii9EW8kI+r6tpvSgC+lP7/Tyng==}
+  /@aws-sdk/middleware-signing@3.310.0:
+    resolution: {integrity: sha512-f9mKq+XMdW207Af3hKjdTnpNhdtwqWuvFs/ZyXoOkp/g1MY1O6L23Jy6i52m29LxbT4AuNRG1oKODfXM0vYVjQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/signature-v4': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-middleware': 3.306.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/signature-v4': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-middleware': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-ssec@3.306.0:
-    resolution: {integrity: sha512-4oXDBsQSPWxyr5K9OjQA1Y+WChGYLD85mvcycd3WqKn9BKsQ9F4enE3XHmRg0qlgXV9CJ/fG1axLC3R9Aa6u/g==}
+  /@aws-sdk/middleware-ssec@3.310.0:
+    resolution: {integrity: sha512-CnEwNKVpd5bXnrCKPaePF8mWTA9ET21OMBb54y9b0fd8K02zoOcdBz4DWfh1SjFD4HkgCdja4egd8l2ivyvqmw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-stack@3.306.0:
-    resolution: {integrity: sha512-G//a6MVSxyFVpOMZ+dzT3+w7XblOd2tRJ5g+/okjn3pNBLbo5o9Hu33K/bz0SQjT/m5mU2F9m0wcdCPYbRPysg==}
+  /@aws-sdk/middleware-stack@3.310.0:
+    resolution: {integrity: sha512-010O1PD+UAcZVKRvqEusE1KJqN96wwrf6QsqbRM0ywsKQ21NDweaHvEDlds2VHpgmofxkRLRu/IDrlPkKRQrRg==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/middleware-user-agent@3.306.0:
-    resolution: {integrity: sha512-tP6I+Lbs68muPfdMA6Rfc+8fYo49nEn9A3RMiOU2COClWsmiZatpbK9UYlqIOxeGB/s2jI7hXmQq6tT2LStLSg==}
+  /@aws-sdk/middleware-user-agent@3.319.0:
+    resolution: {integrity: sha512-ytaLx2dlR5AdMSne6FuDCISVg8hjyKj+cHU20b2CRA/E/z+XXrLrssp4JrCgizRKPPUep0psMIa22Zd6osTT5Q==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-endpoints': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-endpoints': 3.319.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/node-config-provider@3.306.0:
-    resolution: {integrity: sha512-+m+ALxNx5E1zLPPijO1pAbT5tnofLzZFWlnSYBEiOIwzaRU44rLYDqAhgXJkMMbOECkffDrv6ym0oWJIwJI+DA==}
+  /@aws-sdk/node-config-provider@3.310.0:
+    resolution: {integrity: sha512-T/Pp6htc6hq/Cq+MLNDSyiwWCMVF6GqbBbXKVlO5L8rdHx4sq9xPdoPveZhGWrxvkanjA6eCwUp6E0riBOSVng==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/shared-ini-file-loader': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/shared-ini-file-loader': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/node-http-handler@3.306.0:
-    resolution: {integrity: sha512-qvNSIVdGf0pnWEXsAulIqXk7LML25Zc1yxbujxoAj8oX5y+mDhzQdHKrMgc0FuI4RKoEd9px4DYoUbmTWrrxwA==}
+  /@aws-sdk/node-http-handler@3.321.1:
+    resolution: {integrity: sha512-DdQBrtFFDNtzphJIN3s93Vf+qd9LHSzH6WTQRrWoXhTDMHDzSI2Cn+c5KWfk89Nggp/n3+OTwUPQeCiBT5EBuw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/abort-controller': 3.306.0
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/querystring-builder': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/abort-controller': 3.310.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/querystring-builder': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/property-provider@3.306.0:
-    resolution: {integrity: sha512-37PnbjpANjHys0Y+DVmKUz1JbSGZ/mAndZeplTUsFDUtbNwJRw/fDyWUvGC82JWB4gNSP5muWscFvetZnK2l8A==}
+  /@aws-sdk/property-provider@3.310.0:
+    resolution: {integrity: sha512-3lxDb0akV6BBzmFe4nLPaoliQbAifyWJhuvuDOu7e8NzouvpQXs0275w9LePhhcgjKAEVXUIse05ZW2DLbxo/g==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/protocol-http@3.306.0:
-    resolution: {integrity: sha512-6Z8bqB8Ydz/qG7+lJzjwsjIca2w2zp4nZ2HjxMoUm0NBbVXGDx7H9qy9eOUqEiCbdXbsfK2BmVQreLhFLt056Q==}
+  /@aws-sdk/protocol-http@3.310.0:
+    resolution: {integrity: sha512-fgZ1aw/irQtnrsR58pS8ThKOWo57Py3xX6giRvwSgZDEcxHfVzuQjy9yPuV++v04fdmdtgpbGf8WfvAAJ11yXQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/querystring-builder@3.306.0:
-    resolution: {integrity: sha512-kvz6fLwE4KojTxbphuo9JPwKKuhau2mmSurnqhtf77t9+0cOh2uzyYhIUtOFewpLj+qGoh4b2EODlJqczc7IKg==}
+  /@aws-sdk/querystring-builder@3.310.0:
+    resolution: {integrity: sha512-ZHH8GV/80+pWGo7DzsvwvXR5xVxUHXUvPJPFAkhr6nCf78igdoF8gR10ScFoEKbtEapoNTaZlKHPXxpD8aPG7A==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-uri-escape': 3.303.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-uri-escape': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/querystring-parser@3.306.0:
-    resolution: {integrity: sha512-YjOdLcyS/8sNkFPgnxyUx+cM/P2XFGCA2WjQ0e9AXX8xFFkmnY6U5w2EknQ5zyvKy+R/KAV0KAMJBUB+ofjg0A==}
+  /@aws-sdk/querystring-parser@3.310.0:
+    resolution: {integrity: sha512-YkIznoP6lsiIUHinx++/lbb3tlMURGGqMpo0Pnn32zYzGrJXA6eC3D0as2EcMjo55onTfuLcIiX4qzXes2MYOA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/service-error-classification@3.306.0:
-    resolution: {integrity: sha512-lmXIVHWU5J60GmmTgyj79kupWYg5ntyNrUPt1P9FYTsXz+tdk4YYH7/2IxZ1XjBr4jEsN56gfSI0cfT07ztQJA==}
+  /@aws-sdk/service-error-classification@3.310.0:
+    resolution: {integrity: sha512-PuyC7k3qfIKeH2LCnDwbttMOKq3qAx4buvg0yfnJtQOz6t1AR8gsnAq0CjKXXyfkXwNKWTqCpE6lVNUIkXgsMw==}
     engines: {node: '>=14.0.0'}
     dev: false
 
-  /@aws-sdk/shared-ini-file-loader@3.306.0:
-    resolution: {integrity: sha512-mDmBRN+Y0+EBD5megId97UIJGV/rmRsAds22qy0mmVdD3X7qlxn974btXVgfZyda6qw/pX6hgi8X99Qj6Wjb0w==}
+  /@aws-sdk/shared-ini-file-loader@3.310.0:
+    resolution: {integrity: sha512-N0q9pG0xSjQwc690YQND5bofm+4nfUviQ/Ppgan2kU6aU0WUq8KwgHJBto/YEEI+VlrME30jZJnxtOvcZJc2XA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/signature-v4-multi-region@3.306.0:
-    resolution: {integrity: sha512-c+J/7oYZf1MWgEqHyzW6EM25FZfqHer+pMWwyaDCEQFsAuuNeqbvr5MWFrjYpSeF8MpfVbFsEfho5XmjHMq6jw==}
+  /@aws-sdk/signature-v4-multi-region@3.310.0:
+    resolution: {integrity: sha512-q8W+RIomTS/q85Ntgks/CoDElwqkC9+4OCicee5YznNHjQ4gtNWhUkYIyIRWRmXa/qx/AUreW9DM8FAecCOdng==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       '@aws-sdk/signature-v4-crt': ^3.118.0
@@ -1794,137 +1839,137 @@ packages:
       '@aws-sdk/signature-v4-crt':
         optional: true
     dependencies:
-      '@aws-sdk/protocol-http': 3.306.0
-      '@aws-sdk/signature-v4': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/protocol-http': 3.310.0
+      '@aws-sdk/signature-v4': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/signature-v4@3.306.0:
-    resolution: {integrity: sha512-yoQTo6wLirKHg34Zhm8tKmfEaK8fOn+psVdMtRs2vGq3uzKLb+YW5zywnujoVwBvygQTWxiDMwRxDduWAisccA==}
+  /@aws-sdk/signature-v4@3.310.0:
+    resolution: {integrity: sha512-1M60P1ZBNAjCFv9sYW29OF6okktaeibWyW3lMXqzoHF70lHBZh+838iUchznXUA5FLabfn4jBFWMRxlAXJUY2Q==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/is-array-buffer': 3.303.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-hex-encoding': 3.295.0
-      '@aws-sdk/util-middleware': 3.306.0
-      '@aws-sdk/util-uri-escape': 3.303.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/is-array-buffer': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-hex-encoding': 3.310.0
+      '@aws-sdk/util-middleware': 3.310.0
+      '@aws-sdk/util-uri-escape': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/smithy-client@3.306.0:
-    resolution: {integrity: sha512-AFdNkto0Md6laio9t70WtvocoZqVcAydbY5csimXQh+lhKVmy/C+ZcKarDvaa0JD6PjSHb4snYzcINFpHW5LJQ==}
+  /@aws-sdk/smithy-client@3.316.0:
+    resolution: {integrity: sha512-6YXOKbRnXeS8r8RWzuL6JMBolDYM5Wa4fD/VY6x/wK78i2xErHOvqzHgyyeLI1MMw4uqyd4wRNJNWC9TMPduXw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/middleware-stack': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/middleware-stack': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/token-providers@3.306.0:
-    resolution: {integrity: sha512-GQlUx9u+fHLjOJedudLM//j7RSZAip57n59bjn/I3TRVjDs065opNu2xSWMPm1n46kPx6VA5z+DktvuFeAblxQ==}
+  /@aws-sdk/token-providers@3.321.1:
+    resolution: {integrity: sha512-I1sXS4qXirSvgvrOIPf+e1D7GvC83DdeyMxHZvuhHgeMCqDAzToS8OLxOX0enN9xZRHWAQYja8xyeGbDL2I0Zw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/client-sso-oidc': 3.306.0
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/shared-ini-file-loader': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/client-sso-oidc': 3.321.1
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/shared-ini-file-loader': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     transitivePeerDependencies:
       - aws-crt
     dev: false
 
-  /@aws-sdk/types@3.306.0:
-    resolution: {integrity: sha512-RnyknWWpQcRmNH7AsNr89sdhOoltCU/4YEwBMw34Eh+/36l7HfA5PdEKbsOkO7MO4+2g5qmmm/AHcnHRvymApg==}
+  /@aws-sdk/types@3.310.0:
+    resolution: {integrity: sha512-j8eamQJ7YcIhw7fneUfs8LYl3t01k4uHi4ZDmNRgtbmbmTTG3FZc2MotStZnp3nZB6vLiPF1o5aoJxWVvkzS6A==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/url-parser@3.306.0:
-    resolution: {integrity: sha512-mhyOjtycZgxKYo2CoDhDQONuRd5TLfEwmyGWVgFrfubF0LejQ3rkBRLC5zT9TBZ8RJHNlqU2oGdsZCy3JV6Rlw==}
+  /@aws-sdk/url-parser@3.310.0:
+    resolution: {integrity: sha512-mCLnCaSB9rQvAgx33u0DujLvr4d5yEm/W5r789GblwwQnlNXedVu50QRizMLTpltYWyAUoXjJgQnJHmJMaKXhw==}
     dependencies:
-      '@aws-sdk/querystring-parser': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/querystring-parser': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-arn-parser@3.295.0:
-    resolution: {integrity: sha512-kSSVymcbjyQQHvCZaTt1teKKW4MSSMPRdPNxSNO1aLsVwxrWdnAggDrpHwFjvPCRUcKtpThepATOz75PfUm9Bg==}
+  /@aws-sdk/util-arn-parser@3.310.0:
+    resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-base64@3.303.0:
-    resolution: {integrity: sha512-oj+p/GHHPcZEKjiiOHU/CyNQeh8i+8dfMMzU+VGdoK5jHaVG8h2b+V7GPf7I4wDkG2ySCK5b5Jw5NUHwdTJ13Q==}
+  /@aws-sdk/util-base64@3.310.0:
+    resolution: {integrity: sha512-v3+HBKQvqgdzcbL+pFswlx5HQsd9L6ZTlyPVL2LS9nNXnCcR3XgGz9jRskikRUuUvUXtkSG1J88GAOnJ/apTPg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/util-buffer-from': 3.303.0
+      '@aws-sdk/util-buffer-from': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-body-length-browser@3.303.0:
-    resolution: {integrity: sha512-T643m0pKzgjAvPFy4W8zL+aszG3T22U8hb6stlMvT0z++Smv8QfIvkIkXjWyH2KlOt5GKliHwdOv8SAi0FSMJQ==}
+  /@aws-sdk/util-body-length-browser@3.310.0:
+    resolution: {integrity: sha512-sxsC3lPBGfpHtNTUoGXMQXLwjmR0zVpx0rSvzTPAuoVILVsp5AU/w5FphNPxD5OVIjNbZv9KsKTuvNTiZjDp9g==}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-body-length-node@3.303.0:
-    resolution: {integrity: sha512-/hS8z6e18Le60hJr2TUIFoUjUiAsnQsuDn6DxX74GXhMOHeSwZDJ9jHF39quYkNMmAE37GrVH4MI9vE0pN27qw==}
+  /@aws-sdk/util-body-length-node@3.310.0:
+    resolution: {integrity: sha512-2tqGXdyKhyA6w4zz7UPoS8Ip+7sayOg9BwHNidiGm2ikbDxm1YrCfYXvCBdwaJxa4hJfRVz+aL9e+d3GqPI9pQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-buffer-from@3.303.0:
-    resolution: {integrity: sha512-hUU+NW+SW6RNojtAKnnmz+tDShVKlEx2YsS4a5fSfrKRUes+zWz10cxVX0RQfysd3R6tdSHhbjsSj8eCIybheg==}
+  /@aws-sdk/util-buffer-from@3.310.0:
+    resolution: {integrity: sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/is-array-buffer': 3.303.0
+      '@aws-sdk/is-array-buffer': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-config-provider@3.295.0:
-    resolution: {integrity: sha512-/5Dl1aV2yI8YQjqwmg4RTnl/E9NmNsx7HIwBZt+dTcOrM0LMUwczQBFFcLyqCj/qv5y+VsvLoAAA/OiBT7hb3w==}
+  /@aws-sdk/util-config-provider@3.310.0:
+    resolution: {integrity: sha512-xIBaYo8dwiojCw8vnUcIL4Z5tyfb1v3yjqyJKJWV/dqKUFOOS0U591plmXbM+M/QkXyML3ypon1f8+BoaDExrg==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-defaults-mode-browser@3.306.0:
-    resolution: {integrity: sha512-XczPC/klGngMNDcNvThloyeKoPoG61ts1tZVcDbyRaOqmoMH80fn+c6Ah4A/BPzbo8wm1MIA9kqeJI0ypps6qQ==}
+  /@aws-sdk/util-defaults-mode-browser@3.316.0:
+    resolution: {integrity: sha512-6FSqLhYmaihtH2n1s4b2rlLW0ABU8N6VZIfzLfe2ING4PF0MzfaMMhnTFUHVXfKCVGoR8yP6iyFTRCyHGVEL1w==}
     engines: {node: '>= 10.0.0'}
     dependencies:
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/types': 3.310.0
       bowser: 2.11.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-defaults-mode-node@3.306.0:
-    resolution: {integrity: sha512-0hs/cS7Pu4sEO78n0Uv7ybBEFq5j23TOu3QNH+YMzF8n4yuQtaMwNM8DI2s03/pVGXYsPzO7036jREGcu+enXw==}
+  /@aws-sdk/util-defaults-mode-node@3.316.0:
+    resolution: {integrity: sha512-dkYy10hdjPSScXXvnjGpZpnJxllkb6ICHgLMwZ4JczLHhPM12T/4PQ758YN8HS+muiYDGX1Bl2z1jd/bMcewBQ==}
     engines: {node: '>= 10.0.0'}
     dependencies:
-      '@aws-sdk/config-resolver': 3.306.0
-      '@aws-sdk/credential-provider-imds': 3.306.0
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/property-provider': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/config-resolver': 3.310.0
+      '@aws-sdk/credential-provider-imds': 3.310.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/property-provider': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-endpoints@3.306.0:
-    resolution: {integrity: sha512-aPTqU4VGhec8LDhKZrfA3/sBHTYRa0favKEo8aEa/vIZJTNBAFlUhvr5z7peAr8gBOtZZcElzX8PiK3jjn3ILw==}
+  /@aws-sdk/util-endpoints@3.319.0:
+    resolution: {integrity: sha512-3I64UMoYA2e2++oOUJXRcFtYLpLylnZFRltWfPo1B3dLlf+MIWat9djT+mMus+hW1ntLsvAIVu1hLVePJC0gvw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-hex-encoding@3.295.0:
-    resolution: {integrity: sha512-XJcoVo41kHzhe28PBm/rqt5mdCp8R6abwiW9ug1dA6FOoPUO8kBUxDv6xaOmA2hfRvd2ocFfBXaUCBqUowkGcQ==}
+  /@aws-sdk/util-hex-encoding@3.310.0:
+    resolution: {integrity: sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
@@ -1937,59 +1982,59 @@ packages:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-middleware@3.306.0:
-    resolution: {integrity: sha512-14CSm1mTrfSNBGbkZu8vSjXYg7DUMfZc74IinOajcFtTswa/6SyiyhU9DK0a837qqwxSfFGpnE2thVeJIF/7FA==}
+  /@aws-sdk/util-middleware@3.310.0:
+    resolution: {integrity: sha512-FTSUKL/eRb9X6uEZClrTe27QFXUNNp7fxYrPndZwk1hlaOP5ix+MIHBcI7pIiiY/JPfOUmPyZOu+HetlFXjWog==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-retry@3.306.0:
-    resolution: {integrity: sha512-zcgTEIehQAIAm4vBNWfXZpDNbIrDM095vZmpbozQwK/pfDqMGvq7j3r9atKuEGTtoomoGoYwj3x/KEhO6JXJLg==}
+  /@aws-sdk/util-retry@3.310.0:
+    resolution: {integrity: sha512-FwWGhCBLfoivTMUHu1LIn4NjrN9JLJ/aX5aZmbcPIOhZVFJj638j0qDgZXyfvVqBuBZh7M8kGq0Oahy3dp69OA==}
     engines: {node: '>= 14.0.0'}
     dependencies:
-      '@aws-sdk/service-error-classification': 3.306.0
+      '@aws-sdk/service-error-classification': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-stream-browser@3.306.0:
-    resolution: {integrity: sha512-g/UdUlVtMrdctzu8Yk6UKIlN9OtqGyTDC3bCzzr6dNmfdRMuilL5LZCAgusEEzH2MZGTNtw5BbYZBN//UNhxwQ==}
+  /@aws-sdk/util-stream-browser@3.310.0:
+    resolution: {integrity: sha512-bysXZHwFwvbqOTCScCdCnoLk1K3GCo0HRIYEZuL7O7MHrQmfaYRXcaft/p22+GUv9VeFXS/eJJZ5r4u32az94w==}
     dependencies:
-      '@aws-sdk/fetch-http-handler': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-base64': 3.303.0
-      '@aws-sdk/util-hex-encoding': 3.295.0
-      '@aws-sdk/util-utf8': 3.303.0
+      '@aws-sdk/fetch-http-handler': 3.310.0
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-base64': 3.310.0
+      '@aws-sdk/util-hex-encoding': 3.310.0
+      '@aws-sdk/util-utf8': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-stream-node@3.306.0:
-    resolution: {integrity: sha512-hF8P52E3borUp6Z4K8cQOGm+bJgkbkE/VSl0EiO29E563ZiPnT/VBYVICX7MIDNPgHKDN5n/OSVfe1egjhOigQ==}
+  /@aws-sdk/util-stream-node@3.321.1:
+    resolution: {integrity: sha512-jvfff1zeA8q16hQWSC0BGwcHJPCwoh+bwiuAjihfl9q1tFLYuqaTzJzzkL1bntUsbW+y/ac5DO7fWcYPq0jWkw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/node-http-handler': 3.306.0
-      '@aws-sdk/types': 3.306.0
-      '@aws-sdk/util-buffer-from': 3.303.0
+      '@aws-sdk/node-http-handler': 3.321.1
+      '@aws-sdk/types': 3.310.0
+      '@aws-sdk/util-buffer-from': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-uri-escape@3.303.0:
-    resolution: {integrity: sha512-N3ULNuHCL3QzAlCTY+XRRkRQTYCTU8RRuzFCJX0pDpz9t2K+tLT7DbxqupWGNFGl5Xlulf1Is14J3BP/Dx91rA==}
+  /@aws-sdk/util-uri-escape@3.310.0:
+    resolution: {integrity: sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-user-agent-browser@3.306.0:
-    resolution: {integrity: sha512-uZAtpvCasUdWRlB/nEjN0gf6G7810hT50VyWjpd6mQW78myV8M5fu/R03UFAZ+D8fhqqIdzR/IXDY1QUGp8bCA==}
+  /@aws-sdk/util-user-agent-browser@3.310.0:
+    resolution: {integrity: sha512-yU/4QnHHuQ5z3vsUqMQVfYLbZGYwpYblPiuZx4Zo9+x0PBkNjYMqctdDcrpoH9Z2xZiDN16AmQGK1tix117ZKw==}
     dependencies:
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/types': 3.310.0
       bowser: 2.11.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-user-agent-node@3.306.0:
-    resolution: {integrity: sha512-zLp9wIx7FZ0qFLimYW3lJ1uJM5gqxmmcQjNimUaUq/4a1caDkaiF/QeyyMFva+wIjyHRv22P5abUBjIEZrs5WA==}
+  /@aws-sdk/util-user-agent-node@3.310.0:
+    resolution: {integrity: sha512-Ra3pEl+Gn2BpeE7KiDGpi4zj7WJXZA5GXnGo3mjbi9+Y3zrbuhJAbdZO3mO/o7xDgMC6ph4xCTbaSGzU6b6EDg==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       aws-crt: '>=1.0.0'
@@ -1997,8 +2042,8 @@ packages:
       aws-crt:
         optional: true
     dependencies:
-      '@aws-sdk/node-config-provider': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/node-config-provider': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
@@ -2008,25 +2053,25 @@ packages:
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-utf8@3.303.0:
-    resolution: {integrity: sha512-tZXVuMOIONPOuOGBs/XRdzxv6jUvTM620dRFFIHZwlGiW8bo0x0LlonrzDAJZA4e9ZwmxJIj8Ji13WVRBGvZWg==}
+  /@aws-sdk/util-utf8@3.310.0:
+    resolution: {integrity: sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/util-buffer-from': 3.303.0
+      '@aws-sdk/util-buffer-from': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/util-waiter@3.306.0:
-    resolution: {integrity: sha512-/cCmEaxGJOVKHuuzm4zM3aY2Un7pJGyewcd9WWvLjZIoF9jCCqyjmsxM+OXdCjs7NOdo41cValYhILYI+nD8Tg==}
+  /@aws-sdk/util-waiter@3.310.0:
+    resolution: {integrity: sha512-AV5j3guH/Y4REu+Qh3eXQU9igljHuU4XjX2sADAgf54C0kkhcCCkkiuzk3IsX089nyJCqIcj5idbjdvpnH88Vw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      '@aws-sdk/abort-controller': 3.306.0
-      '@aws-sdk/types': 3.306.0
+      '@aws-sdk/abort-controller': 3.310.0
+      '@aws-sdk/types': 3.310.0
       tslib: 2.5.0
     dev: false
 
-  /@aws-sdk/xml-builder@3.303.0:
-    resolution: {integrity: sha512-Oht8XdmCkLhwZx2WTjOOLN8rt9000zJS4Hehv9NG7+kKfaA6sKFGIculmumaS+h8hAwWFndtgpOTlKC95zHSWQ==}
+  /@aws-sdk/xml-builder@3.310.0:
+    resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==}
     engines: {node: '>=14.0.0'}
     dependencies:
       tslib: 2.5.0
@@ -2039,11 +2084,6 @@ packages:
       '@babel/highlight': 7.18.6
     dev: true
 
-  /@babel/compat-data@7.20.10:
-    resolution: {integrity: sha512-sEnuDPpOJR/fcafHMjpcpGN5M2jbUGUHwmuWKM/YdPzeEDJg8bgmbcWQFUfE32MQjti1koACvoPVsDe8Uq+idg==}
-    engines: {node: '>=6.9.0'}
-    dev: true
-
   /@babel/compat-data@7.21.4:
     resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==}
     engines: {node: '>=6.9.0'}
@@ -2056,13 +2096,13 @@ packages:
       '@ampproject/remapping': 2.2.0
       '@babel/code-frame': 7.18.6
       '@babel/generator': 7.21.3
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3)
+      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
       '@babel/helper-module-transforms': 7.21.2
       '@babel/helpers': 7.21.0
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/template': 7.20.7
       '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
       convert-source-map: 1.9.0
       debug: 4.3.4(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
@@ -2076,7 +2116,7 @@ packages:
     resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
       '@jridgewell/gen-mapping': 0.3.2
       '@jridgewell/trace-mapping': 0.3.17
       jsesc: 2.5.2
@@ -2097,20 +2137,6 @@ packages:
       '@babel/types': 7.21.4
     dev: true
 
-  /@babel/helper-compilation-targets@7.20.7(@babel/core@7.21.3):
-    resolution: {integrity: sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/compat-data': 7.20.10
-      '@babel/core': 7.21.3
-      '@babel/helper-validator-option': 7.21.0
-      browserslist: 4.21.5
-      lru-cache: 5.1.1
-      semver: 6.3.0
-    dev: true
-
   /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.3):
     resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==}
     engines: {node: '>=6.9.0'}
@@ -2342,12 +2368,12 @@ packages:
     dependencies:
       '@babel/types': 7.21.4
 
-  /@babel/parser@7.21.3:
-    resolution: {integrity: sha512-lobG0d7aOfQRXh8AyklEAgZGvA4FShxo6xQbUrrT/cNBPUdIDojlokwJsQyCC/eKia7ifqM0yP+2DRZ4WKw2RQ==}
+  /@babel/parser@7.21.4:
+    resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==}
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
 
   /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
@@ -3052,7 +3078,7 @@ packages:
       '@babel/helper-module-imports': 7.18.6
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3)
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
     dev: true
 
   /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.21.3):
@@ -3163,92 +3189,6 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/preset-env@7.20.2(@babel/core@7.21.3):
-    resolution: {integrity: sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/compat-data': 7.20.10
-      '@babel/core': 7.21.3
-      '@babel/helper-compilation-targets': 7.20.7(@babel/core@7.21.3)
-      '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-validator-option': 7.21.0
-      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.21.3)
-      '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-private-property-in-object': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.3)
-      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.3)
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.21.3)
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.21.3)
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.3)
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.3)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.3)
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.21.3)
-      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.3)
-      '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-transform-async-to-generator': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-block-scoping': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-transform-classes': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-transform-destructuring': 7.21.3(@babel/core@7.21.3)
-      '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.21.3)
-      '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-for-of': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.21.3)
-      '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.21.3)
-      '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-modules-amd': 7.20.11(@babel/core@7.21.3)
-      '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3)
-      '@babel/plugin-transform-modules-systemjs': 7.20.11(@babel/core@7.21.3)
-      '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5(@babel/core@7.21.3)
-      '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.3)
-      '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-regenerator': 7.20.5(@babel/core@7.21.3)
-      '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.21.3)
-      '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.21.3)
-      '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.21.3)
-      '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.21.3)
-      '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.21.3)
-      '@babel/preset-modules': 0.1.5(@babel/core@7.21.3)
-      '@babel/types': 7.21.3
-      babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3)
-      babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.3)
-      babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.3)
-      core-js-compat: 3.29.1
-      semver: 6.3.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@babel/preset-env@7.21.4(@babel/core@7.21.3):
     resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==}
     engines: {node: '>=6.9.0'}
@@ -3403,14 +3343,13 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       regenerator-runtime: 0.13.11
-    dev: true
 
   /@babel/template@7.20.7:
     resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/code-frame': 7.18.6
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/types': 7.21.4
     dev: true
 
@@ -3424,7 +3363,7 @@ packages:
       '@babel/helper-function-name': 7.21.0
       '@babel/helper-hoist-variables': 7.18.6
       '@babel/helper-split-export-declaration': 7.18.6
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/types': 7.21.4
       debug: 4.3.4(supports-color@8.1.1)
       globals: 11.12.0
@@ -3432,14 +3371,6 @@ packages:
       - supports-color
     dev: true
 
-  /@babel/types@7.21.3:
-    resolution: {integrity: sha512-sBGdETxC+/M4o/zKC0sl6sjWv62WFR/uzxrJ6uYyMLZOUlPnwzw0tKgVHOXxaAd5l2g8pEDM5RZ495GPQI77kg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-string-parser': 7.19.4
-      '@babel/helper-validator-identifier': 7.19.1
-      to-fast-properties: 2.0.0
-
   /@babel/types@7.21.4:
     resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==}
     engines: {node: '>=6.9.0'}
@@ -3456,26 +3387,29 @@ packages:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
 
-  /@bull-board/api@5.0.0:
-    resolution: {integrity: sha512-nOB8TUSeX8h2wgMxmPZwy0p3XqRX/DotR71M5FT5OS/bZCZIKRpEc36TIeKPimZli6voFyX+rsEqVhGenpIKUQ==}
+  /@bull-board/api@5.1.2(@bull-board/ui@5.1.2):
+    resolution: {integrity: sha512-NLV88eDnOMd0XuYUNBcaYL6UdxbGYY91kP+P5Jled6CGcYmXLXv/mzAopxmgctqmP07cjC2EXN/4Oc7dyalDqA==}
+    peerDependencies:
+      '@bull-board/ui': 5.1.2
     dependencies:
+      '@bull-board/ui': 5.1.2
       redis-info: 3.1.0
     dev: false
 
-  /@bull-board/fastify@5.0.0:
-    resolution: {integrity: sha512-g7eLQhap9HiRjWv3NkgbVrdiG8RDUwyA3qDW2jES1dRcpWPBD9/3rxESwzuIoxa2h0OJ34VLY9hsgpu0vRyhZg==}
+  /@bull-board/fastify@5.1.2:
+    resolution: {integrity: sha512-qiURhcqMfER5hp4RtgepMDbPj5H4ZKNOgK+7RIG3bd3d0tBoLjXzooXFryxzd6w130pXU9/crUMtcMP+Ulaj6g==}
     dependencies:
-      '@bull-board/api': 5.0.0
-      '@bull-board/ui': 5.0.0
-      '@fastify/static': 6.10.0
+      '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2)
+      '@bull-board/ui': 5.1.2
+      '@fastify/static': 6.10.1
       '@fastify/view': 7.4.1
       ejs: 3.1.8
     dev: false
 
-  /@bull-board/ui@5.0.0:
-    resolution: {integrity: sha512-WznLX8dGthUWimZDqN5Zft3Axp6SStlI244BhiQYXB7xXZRMgaihNZxh7vgpwg0Osmw/gPFvAI+NFVGc3tvj9g==}
+  /@bull-board/ui@5.1.2:
+    resolution: {integrity: sha512-DXXbKA4NLo5D19Vssrg4pPFaFjXVzjFN0ht4GVuoJQejy7t/RVrWzZCjdyVuSiOFTlG3SyB39zW5a95Q5EXUTg==}
     dependencies:
-      '@bull-board/api': 5.0.0
+      '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2)
     dev: false
 
   /@canvas/image-data@1.0.0:
@@ -3637,7 +3571,6 @@ packages:
   /@discoveryjs/json-ext@0.5.7:
     resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==}
     engines: {node: '>=10.0.0'}
-    dev: true
 
   /@emotion/use-insertion-effect-with-fallbacks@1.0.0(react@18.2.0):
     resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==}
@@ -3647,365 +3580,189 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@esbuild/android-arm64@0.17.14:
-    resolution: {integrity: sha512-eLOpPO1RvtsP71afiFTvS7tVFShJBCT0txiv/xjFBo5a7R7Gjw7X0IgIaFoLKhqXYAXhahoXm7qAmRXhY4guJg==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [android]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/android-arm64@0.17.15:
-    resolution: {integrity: sha512-0kOB6Y7Br3KDVgHeg8PRcvfLkq+AccreK///B4Z6fNZGr/tNHX0z2VywCc7PTeWp+bPvjA5WMvNXltHw5QjAIA==}
+  /@esbuild/android-arm64@0.17.18:
+    resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@esbuild/android-arm@0.17.14:
-    resolution: {integrity: sha512-0CnlwnjDU8cks0yJLXfkaU/uoLyRf9VZJs4p1PskBr2AlAHeEsFEwJEo0of/Z3g+ilw5mpyDwThlxzNEIxOE4g==}
-    engines: {node: '>=12'}
-    cpu: [arm]
-    os: [android]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/android-arm@0.17.15:
-    resolution: {integrity: sha512-sRSOVlLawAktpMvDyJIkdLI/c/kdRTOqo8t6ImVxg8yT7LQDUYV5Rp2FKeEosLr6ZCja9UjYAzyRSxGteSJPYg==}
+  /@esbuild/android-arm@0.17.18:
+    resolution: {integrity: sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@esbuild/android-x64@0.17.14:
-    resolution: {integrity: sha512-nrfQYWBfLGfSGLvRVlt6xi63B5IbfHm3tZCdu/82zuFPQ7zez4XjmRtF/wIRYbJQ/DsZrxJdEvYFE67avYXyng==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [android]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/android-x64@0.17.15:
-    resolution: {integrity: sha512-MzDqnNajQZ63YkaUWVl9uuhcWyEyh69HGpMIrf+acR4otMkfLJ4sUCxqwbCyPGicE9dVlrysI3lMcDBjGiBBcQ==}
+  /@esbuild/android-x64@0.17.18:
+    resolution: {integrity: sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@esbuild/darwin-arm64@0.17.14:
-    resolution: {integrity: sha512-eoSjEuDsU1ROwgBH/c+fZzuSyJUVXQTOIN9xuLs9dE/9HbV/A5IqdXHU1p2OfIMwBwOYJ9SFVGGldxeRCUJFyw==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [darwin]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/darwin-arm64@0.17.15:
-    resolution: {integrity: sha512-7siLjBc88Z4+6qkMDxPT2juf2e8SJxmsbNVKFY2ifWCDT72v5YJz9arlvBw5oB4W/e61H1+HDB/jnu8nNg0rLA==}
+  /@esbuild/darwin-arm64@0.17.18:
+    resolution: {integrity: sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@esbuild/darwin-x64@0.17.14:
-    resolution: {integrity: sha512-zN0U8RWfrDttdFNkHqFYZtOH8hdi22z0pFm0aIJPsNC4QQZv7je8DWCX5iA4Zx6tRhS0CCc0XC2m7wKsbWEo5g==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [darwin]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/darwin-x64@0.17.15:
-    resolution: {integrity: sha512-NbImBas2rXwYI52BOKTW342Tm3LTeVlaOQ4QPZ7XuWNKiO226DisFk/RyPk3T0CKZkKMuU69yOvlapJEmax7cg==}
+  /@esbuild/darwin-x64@0.17.18:
+    resolution: {integrity: sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@esbuild/freebsd-arm64@0.17.14:
-    resolution: {integrity: sha512-z0VcD4ibeZWVQCW1O7szaLxGsx54gcCnajEJMdYoYjLiq4g1jrP2lMq6pk71dbS5+7op/L2Aod+erw+EUr28/A==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [freebsd]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/freebsd-arm64@0.17.15:
-    resolution: {integrity: sha512-Xk9xMDjBVG6CfgoqlVczHAdJnCs0/oeFOspFap5NkYAmRCT2qTn1vJWA2f419iMtsHSLm+O8B6SLV/HlY5cYKg==}
+  /@esbuild/freebsd-arm64@0.17.18:
+    resolution: {integrity: sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [freebsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/freebsd-x64@0.17.14:
-    resolution: {integrity: sha512-hd9mPcxfTgJlolrPlcXkQk9BMwNBvNBsVaUe5eNUqXut6weDQH8whcNaKNF2RO8NbpT6GY8rHOK2A9y++s+ehw==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [freebsd]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/freebsd-x64@0.17.15:
-    resolution: {integrity: sha512-3TWAnnEOdclvb2pnfsTWtdwthPfOz7qAfcwDLcfZyGJwm1SRZIMOeB5FODVhnM93mFSPsHB9b/PmxNNbSnd0RQ==}
+  /@esbuild/freebsd-x64@0.17.18:
+    resolution: {integrity: sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [freebsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-arm64@0.17.14:
-    resolution: {integrity: sha512-FhAMNYOq3Iblcj9i+K0l1Fp/MHt+zBeRu/Qkf0LtrcFu3T45jcwB6A1iMsemQ42vR3GBhjNZJZTaCe3VFPbn9g==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [linux]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/linux-arm64@0.17.15:
-    resolution: {integrity: sha512-T0MVnYw9KT6b83/SqyznTs/3Jg2ODWrZfNccg11XjDehIved2oQfrX/wVuev9N936BpMRaTR9I1J0tdGgUgpJA==}
+  /@esbuild/linux-arm64@0.17.18:
+    resolution: {integrity: sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-arm@0.17.14:
-    resolution: {integrity: sha512-BNTl+wSJ1omsH8s3TkQmIIIQHwvwJrU9u1ggb9XU2KTVM4TmthRIVyxSp2qxROJHhZuW/r8fht46/QE8hU8Qvg==}
+  /@esbuild/linux-arm@0.17.18:
+    resolution: {integrity: sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-arm@0.17.15:
-    resolution: {integrity: sha512-MLTgiXWEMAMr8nmS9Gigx43zPRmEfeBfGCwxFQEMgJ5MC53QKajaclW6XDPjwJvhbebv+RzK05TQjvH3/aM4Xw==}
+  /@esbuild/linux-ia32@0.17.18:
+    resolution: {integrity: sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==}
     engines: {node: '>=12'}
-    cpu: [arm]
+    cpu: [ia32]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-ia32@0.17.14:
-    resolution: {integrity: sha512-91OK/lQ5y2v7AsmnFT+0EyxdPTNhov3y2CWMdizyMfxSxRqHazXdzgBKtlmkU2KYIc+9ZK3Vwp2KyXogEATYxQ==}
+  /@esbuild/linux-loong64@0.17.18:
+    resolution: {integrity: sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==}
     engines: {node: '>=12'}
-    cpu: [ia32]
+    cpu: [loong64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-ia32@0.17.15:
-    resolution: {integrity: sha512-wp02sHs015T23zsQtU4Cj57WiteiuASHlD7rXjKUyAGYzlOKDAjqK6bk5dMi2QEl/KVOcsjwL36kD+WW7vJt8Q==}
+  /@esbuild/linux-mips64el@0.17.18:
+    resolution: {integrity: sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==}
     engines: {node: '>=12'}
-    cpu: [ia32]
+    cpu: [mips64el]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-loong64@0.17.14:
-    resolution: {integrity: sha512-vp15H+5NR6hubNgMluqqKza85HcGJgq7t6rMH7O3Y6ApiOWPkvW2AJfNojUQimfTp6OUrACUXfR4hmpcENXoMQ==}
+  /@esbuild/linux-ppc64@0.17.18:
+    resolution: {integrity: sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==}
     engines: {node: '>=12'}
-    cpu: [loong64]
+    cpu: [ppc64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-loong64@0.17.15:
-    resolution: {integrity: sha512-k7FsUJjGGSxwnBmMh8d7IbObWu+sF/qbwc+xKZkBe/lTAF16RqxRCnNHA7QTd3oS2AfGBAnHlXL67shV5bBThQ==}
+  /@esbuild/linux-riscv64@0.17.18:
+    resolution: {integrity: sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==}
     engines: {node: '>=12'}
-    cpu: [loong64]
+    cpu: [riscv64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-mips64el@0.17.14:
-    resolution: {integrity: sha512-90TOdFV7N+fgi6c2+GO9ochEkmm9kBAKnuD5e08GQMgMINOdOFHuYLPQ91RYVrnWwQ5683sJKuLi9l4SsbJ7Hg==}
+  /@esbuild/linux-s390x@0.17.18:
+    resolution: {integrity: sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==}
     engines: {node: '>=12'}
-    cpu: [mips64el]
+    cpu: [s390x]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-mips64el@0.17.15:
-    resolution: {integrity: sha512-ZLWk6czDdog+Q9kE/Jfbilu24vEe/iW/Sj2d8EVsmiixQ1rM2RKH2n36qfxK4e8tVcaXkvuV3mU5zTZviE+NVQ==}
+  /@esbuild/linux-x64@0.17.18:
+    resolution: {integrity: sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==}
     engines: {node: '>=12'}
-    cpu: [mips64el]
+    cpu: [x64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-ppc64@0.17.14:
-    resolution: {integrity: sha512-NnBGeoqKkTugpBOBZZoktQQ1Yqb7aHKmHxsw43NddPB2YWLAlpb7THZIzsRsTr0Xw3nqiPxbA1H31ZMOG+VVPQ==}
+  /@esbuild/netbsd-x64@0.17.18:
+    resolution: {integrity: sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==}
     engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [linux]
+    cpu: [x64]
+    os: [netbsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-ppc64@0.17.15:
-    resolution: {integrity: sha512-mY6dPkIRAiFHRsGfOYZC8Q9rmr8vOBZBme0/j15zFUKM99d4ILY4WpOC7i/LqoY+RE7KaMaSfvY8CqjJtuO4xg==}
+  /@esbuild/openbsd-x64@0.17.18:
+    resolution: {integrity: sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==}
     engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [linux]
+    cpu: [x64]
+    os: [openbsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-riscv64@0.17.14:
-    resolution: {integrity: sha512-0qdlKScLXA8MGVy21JUKvMzCYWovctuP8KKqhtE5A6IVPq4onxXhSuhwDd2g5sRCzNDlDjitc5sX31BzDoL5Fw==}
+  /@esbuild/sunos-x64@0.17.18:
+    resolution: {integrity: sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==}
     engines: {node: '>=12'}
-    cpu: [riscv64]
-    os: [linux]
+    cpu: [x64]
+    os: [sunos]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-riscv64@0.17.15:
-    resolution: {integrity: sha512-EcyUtxffdDtWjjwIH8sKzpDRLcVtqANooMNASO59y+xmqqRYBBM7xVLQhqF7nksIbm2yHABptoioS9RAbVMWVA==}
+  /@esbuild/win32-arm64@0.17.18:
+    resolution: {integrity: sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==}
     engines: {node: '>=12'}
-    cpu: [riscv64]
-    os: [linux]
+    cpu: [arm64]
+    os: [win32]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-s390x@0.17.14:
-    resolution: {integrity: sha512-Hdm2Jo1yaaOro4v3+6/zJk6ygCqIZuSDJHdHaf8nVH/tfOuoEX5Riv03Ka15LmQBYJObUTNS1UdyoMk0WUn9Ww==}
+  /@esbuild/win32-ia32@0.17.18:
+    resolution: {integrity: sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==}
     engines: {node: '>=12'}
-    cpu: [s390x]
-    os: [linux]
+    cpu: [ia32]
+    os: [win32]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-s390x@0.17.15:
-    resolution: {integrity: sha512-BuS6Jx/ezxFuHxgsfvz7T4g4YlVrmCmg7UAwboeyNNg0OzNzKsIZXpr3Sb/ZREDXWgt48RO4UQRDBxJN3B9Rbg==}
+  /@esbuild/win32-x64@0.17.18:
+    resolution: {integrity: sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==}
     engines: {node: '>=12'}
-    cpu: [s390x]
-    os: [linux]
+    cpu: [x64]
+    os: [win32]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-x64@0.17.14:
-    resolution: {integrity: sha512-8KHF17OstlK4DuzeF/KmSgzrTWQrkWj5boluiiq7kvJCiQVzUrmSkaBvcLB2UgHpKENO2i6BthPkmUhNDaJsVw==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [linux]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/linux-x64@0.17.15:
-    resolution: {integrity: sha512-JsdS0EgEViwuKsw5tiJQo9UdQdUJYuB+Mf6HxtJSPN35vez1hlrNb1KajvKWF5Sa35j17+rW1ECEO9iNrIXbNg==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [linux]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/netbsd-x64@0.17.14:
-    resolution: {integrity: sha512-nVwpqvb3yyXztxIT2+VsxJhB5GCgzPdk1n0HHSnchRAcxqKO6ghXwHhJnr0j/B+5FSyEqSxF4q03rbA2fKXtUQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [netbsd]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/netbsd-x64@0.17.15:
-    resolution: {integrity: sha512-R6fKjtUysYGym6uXf6qyNephVUQAGtf3n2RCsOST/neIwPqRWcnc3ogcielOd6pT+J0RDR1RGcy0ZY7d3uHVLA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [netbsd]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/openbsd-x64@0.17.14:
-    resolution: {integrity: sha512-1RZ7uQQ9zcy/GSAJL1xPdN7NDdOOtNEGiJalg/MOzeakZeTrgH/DoCkbq7TaPDiPhWqnDF+4bnydxRqQD7il6g==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [openbsd]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/openbsd-x64@0.17.15:
-    resolution: {integrity: sha512-mVD4PGc26b8PI60QaPUltYKeSX0wxuy0AltC+WCTFwvKCq2+OgLP4+fFd+hZXzO2xW1HPKcytZBdjqL6FQFa7w==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [openbsd]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/sunos-x64@0.17.14:
-    resolution: {integrity: sha512-nqMjDsFwv7vp7msrwWRysnM38Sd44PKmW8EzV01YzDBTcTWUpczQg6mGao9VLicXSgW/iookNK6AxeogNVNDZA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [sunos]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/sunos-x64@0.17.15:
-    resolution: {integrity: sha512-U6tYPovOkw3459t2CBwGcFYfFRjivcJJc1WC8Q3funIwX8x4fP+R6xL/QuTPNGOblbq/EUDxj9GU+dWKX0oWlQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [sunos]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/win32-arm64@0.17.14:
-    resolution: {integrity: sha512-xrD0mccTKRBBIotrITV7WVQAwNJ5+1va6L0H9zN92v2yEdjfAN7864cUaZwJS7JPEs53bDTzKFbfqVlG2HhyKQ==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [win32]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/win32-arm64@0.17.15:
-    resolution: {integrity: sha512-W+Z5F++wgKAleDABemiyXVnzXgvRFs+GVKThSI+mGgleLWluv0D7Diz4oQpgdpNzh4i2nNDzQtWbjJiqutRp6Q==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [win32]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/win32-ia32@0.17.14:
-    resolution: {integrity: sha512-nXpkz9bbJrLLyUTYtRotSS3t5b+FOuljg8LgLdINWFs3FfqZMtbnBCZFUmBzQPyxqU87F8Av+3Nco/M3hEcu1w==}
-    engines: {node: '>=12'}
-    cpu: [ia32]
-    os: [win32]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/win32-ia32@0.17.15:
-    resolution: {integrity: sha512-Muz/+uGgheShKGqSVS1KsHtCyEzcdOn/W/Xbh6H91Etm+wiIfwZaBn1W58MeGtfI8WA961YMHFYTthBdQs4t+w==}
-    engines: {node: '>=12'}
-    cpu: [ia32]
-    os: [win32]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/win32-x64@0.17.14:
-    resolution: {integrity: sha512-gPQmsi2DKTaEgG14hc3CHXHp62k8g6qr0Pas+I4lUxRMugGSATh/Bi8Dgusoz9IQ0IfdrvLpco6kujEIBoaogA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [win32]
-    requiresBuild: true
-    optional: true
-
-  /@esbuild/win32-x64@0.17.15:
-    resolution: {integrity: sha512-DjDa9ywLUUmjhV2Y9wUTIF+1XsmuFGvZoCmOWkli1XcNAh5t25cc7fgsCx4Zi/Uurep3TTLyDiKATgGEg61pkA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [win32]
-    requiresBuild: true
-    optional: true
-
-  /@eslint-community/eslint-utils@4.4.0(eslint@8.37.0):
+  /@eslint-community/eslint-utils@4.4.0(eslint@8.39.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.37.0
+      eslint: 8.39.0
       eslint-visitor-keys: 3.4.0
     dev: true
 
@@ -4031,8 +3788,8 @@ packages:
       - supports-color
     dev: true
 
-  /@eslint/js@8.37.0:
-    resolution: {integrity: sha512-x5vzdtOOGgFVDCUs81QRB2+liax8rFg3+7hqM+QhBG0/G3F1ZsoYl97UrqgHgQ9KKT7G6c4V+aTUCgu/n22v1A==}
+  /@eslint/js@8.39.0:
+    resolution: {integrity: sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
@@ -4089,10 +3846,10 @@ packages:
     resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==}
     dev: false
 
-  /@fastify/fast-json-stringify-compiler@4.2.0:
-    resolution: {integrity: sha512-ypZynRvXA3dibfPykQN3RB5wBdEUgSGgny8Qc6k163wYPLD4mEGEDkACp+00YmqkGvIm8D/xYoHajwyEdWD/eg==}
+  /@fastify/fast-json-stringify-compiler@4.3.0:
+    resolution: {integrity: sha512-aZAXGYo6m22Fk1zZzEUKBvut/CIIQe/BapEORnxiD5Qr0kPHqqI69NtEMCme74h+at72sPhbkb4ZrLd1W3KRLA==}
     dependencies:
-      fast-json-stringify: 5.5.0
+      fast-json-stringify: 5.7.0
     dev: false
 
   /@fastify/http-proxy@9.0.0:
@@ -4106,8 +3863,8 @@ packages:
       - utf-8-validate
     dev: false
 
-  /@fastify/multipart@7.5.0:
-    resolution: {integrity: sha512-cAQzz+vVRaNomTppActeqA3xZstF18WK9gjwCMAkoU6xKFljsnKewdGw2RkcYAaiWgFzGBQUD8owSKPFgzhJAA==}
+  /@fastify/multipart@7.6.0:
+    resolution: {integrity: sha512-mQoz0SMxk1WAYLYvQypoj3GYhCbmhnFl2LdleTzKQ4HgPfhVcMT014uln2wgzRh2y+gL74b/+j0mMVgvOKpqpQ==}
     dependencies:
       '@fastify/busboy': 1.1.0
       '@fastify/deepmerge': 1.3.0
@@ -4141,8 +3898,8 @@ packages:
       mime: 3.0.0
     dev: false
 
-  /@fastify/static@6.10.0:
-    resolution: {integrity: sha512-TGruNm6ZabkQz2oRNoarPnY2BvS9i9DNf8Nn1aDcZp+WjOQRPCq0Wy2ko78yGB5JHytdCWoHpprc128QtLl8hw==}
+  /@fastify/static@6.10.1:
+    resolution: {integrity: sha512-DNnG+5QenQcTQw37qk0/191STThnN6SbU+2XMpWtpYR3gQUfUvMax14jTT/jqNINNbCkQJaKMnPtpFPKo4/68g==}
     dependencies:
       '@fastify/accept-negotiator': 1.0.0
       '@fastify/send': 2.0.1
@@ -4218,7 +3975,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       chalk: 4.1.2
       jest-message-util: 29.5.0
       jest-util: 29.5.0
@@ -4239,14 +3996,14 @@ packages:
       '@jest/test-result': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       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.5.0
-      jest-config: 29.5.0(@types/node@18.15.11)
+      jest-config: 29.5.0(@types/node@18.16.3)
       jest-haste-map: 29.5.0
       jest-message-util: 29.5.0
       jest-regex-util: 29.4.3
@@ -4280,7 +4037,7 @@ packages:
     dependencies:
       '@jest/fake-timers': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       jest-mock: 29.5.0
     dev: true
 
@@ -4307,7 +4064,7 @@ packages:
     dependencies:
       '@jest/types': 29.5.0
       '@sinonjs/fake-timers': 10.0.2
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       jest-message-util: 29.5.0
       jest-mock: 29.5.0
       jest-util: 29.5.0
@@ -4340,7 +4097,7 @@ packages:
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
       '@jridgewell/trace-mapping': 0.3.17
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       chalk: 4.1.2
       collect-v8-coverage: 1.0.1
       exit: 0.1.2
@@ -4434,7 +4191,7 @@ packages:
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -4446,12 +4203,12 @@ packages:
       '@jest/schemas': 29.4.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/yargs': 17.0.19
       chalk: 4.1.2
     dev: true
 
-  /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.3)(vite@4.2.1):
+  /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.4)(vite@4.3.4):
     resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==}
     peerDependencies:
       typescript: '>= 4.3.x'
@@ -4463,9 +4220,9 @@ packages:
       glob: 7.2.3
       glob-promise: 4.2.2(glob@7.2.3)
       magic-string: 0.27.0
-      react-docgen-typescript: 2.2.2(typescript@5.0.3)
-      typescript: 5.0.3
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      react-docgen-typescript: 2.2.2(typescript@5.0.4)
+      typescript: 5.0.4
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
     dev: true
 
   /@jridgewell/gen-mapping@0.1.1:
@@ -4528,6 +4285,7 @@ packages:
 
   /@mapbox/node-pre-gyp@1.0.9:
     resolution: {integrity: sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==}
+    hasBin: true
     dependencies:
       detect-libc: 2.0.1
       https-proxy-agent: 5.0.1
@@ -4536,7 +4294,7 @@ packages:
       nopt: 5.0.0
       npmlog: 5.0.1
       rimraf: 3.0.2
-      semver: 7.3.8
+      semver: 7.5.0
       tar: 6.1.13
     transitivePeerDependencies:
       - encoding
@@ -4554,24 +4312,24 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@microsoft/api-extractor-model@7.26.4(@types/node@18.15.11):
-    resolution: {integrity: sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==}
+  /@microsoft/api-extractor-model@7.26.7(@types/node@18.16.3):
+    resolution: {integrity: sha512-rx3Tq632VG3ddR74kPuPbv1qmUgO2IuCvn1z16hbNWNS5RhnTQqNPWIm7NVoi6lCh2E7uxzfmdnWXIXiJhM5IQ==}
     dependencies:
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.55.2(@types/node@18.15.11)
+      '@rushstack/node-core-library': 3.58.0(@types/node@18.16.3)
     transitivePeerDependencies:
       - '@types/node'
     dev: true
 
-  /@microsoft/api-extractor@7.34.4(@types/node@18.15.11):
-    resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==}
+  /@microsoft/api-extractor@7.34.7(@types/node@18.16.3):
+    resolution: {integrity: sha512-8CrrYyOBWqc4XFviR1KSuHSlmJjlnC5CVpPkcFB8HXiLABUVaVWFTsOzJIwUU6z8mc4BZPZ8tSGAg/mwFDU31Q==}
     hasBin: true
     dependencies:
-      '@microsoft/api-extractor-model': 7.26.4(@types/node@18.15.11)
+      '@microsoft/api-extractor-model': 7.26.7(@types/node@18.16.3)
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.55.2(@types/node@18.15.11)
+      '@rushstack/node-core-library': 3.58.0(@types/node@18.16.3)
       '@rushstack/rig-package': 0.3.18
       '@rushstack/ts-command-line': 4.13.2
       colors: 1.2.5
@@ -4685,7 +4443,7 @@ packages:
       tar-fs: 2.1.1
     dev: true
 
-  /@nestjs/common@9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0):
+  /@nestjs/common@9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1):
     resolution: {integrity: sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==}
     peerDependencies:
       cache-manager: <=5
@@ -4703,12 +4461,12 @@ packages:
     dependencies:
       iterare: 1.2.1
       reflect-metadata: 0.1.13
-      rxjs: 7.8.0
+      rxjs: 7.8.1
       tslib: 2.5.0
       uid: 2.0.2
     dev: false
 
-  /@nestjs/core@9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.0):
+  /@nestjs/core@9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1):
     resolution: {integrity: sha512-yTLryCgFD0462wPe4HIzhyTcDgibt8Stfwb5YzcX7Ma0NM4m8uBIpcPG109KBubp8ZmV85e5mw4rl20qLQQVsQ==}
     requiresBuild: true
     peerDependencies:
@@ -4726,13 +4484,13 @@ packages:
       '@nestjs/websockets':
         optional: true
     dependencies:
-      '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0)
+      '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nuxtjs/opencollective': 0.3.2
       fast-safe-stringify: 2.1.1
       iterare: 1.2.1
       path-to-regexp: 3.2.0
       reflect-metadata: 0.1.13
-      rxjs: 7.8.0
+      rxjs: 7.8.1
       tslib: 2.5.0
       uid: 2.0.2
     transitivePeerDependencies:
@@ -4752,8 +4510,8 @@ packages:
       '@nestjs/platform-express':
         optional: true
     dependencies:
-      '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.0)
-      '@nestjs/core': 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.0)
+      '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@nestjs/core': 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)
       tslib: 2.5.0
     dev: false
 
@@ -4780,7 +4538,7 @@ packages:
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
     dependencies:
       '@gar/promisify': 1.1.3
-      semver: 7.3.8
+      semver: 7.5.0
     dev: false
 
   /@npmcli/move-file@2.0.1:
@@ -4883,8 +4641,8 @@ packages:
       '@redis/client': 1.4.2
     dev: true
 
-  /@rollup/plugin-alias@4.0.3(rollup@3.20.2):
-    resolution: {integrity: sha512-ZuDWE1q4PQDhvm/zc5Prun8sBpLJy41DMptYrS6MhAy9s9kL/doN1613BWfEchGVfKxzliJ3BjbOPizXX38DbQ==}
+  /@rollup/plugin-alias@5.0.0(rollup@3.21.3):
+    resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
       rollup: ^1.20.0||^2.0.0||^3.0.0
@@ -4892,11 +4650,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 3.20.2
+      rollup: 3.21.3
       slash: 4.0.0
     dev: false
 
-  /@rollup/plugin-json@6.0.0(rollup@3.20.2):
+  /@rollup/plugin-json@6.0.0(rollup@3.21.3):
     resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -4905,8 +4663,22 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.2(rollup@3.20.2)
-      rollup: 3.20.2
+      '@rollup/pluginutils': 5.0.2(rollup@3.21.3)
+      rollup: 3.21.3
+    dev: false
+
+  /@rollup/plugin-replace@5.0.2(rollup@3.21.3):
+    resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      rollup: ^1.20.0||^2.0.0||^3.0.0
+    peerDependenciesMeta:
+      rollup:
+        optional: true
+    dependencies:
+      '@rollup/pluginutils': 5.0.2(rollup@3.21.3)
+      magic-string: 0.27.0
+      rollup: 3.21.3
     dev: false
 
   /@rollup/pluginutils@4.2.1:
@@ -4917,7 +4689,7 @@ packages:
       picomatch: 2.3.1
     dev: true
 
-  /@rollup/pluginutils@5.0.2(rollup@3.20.2):
+  /@rollup/pluginutils@5.0.2(rollup@3.21.3):
     resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -4926,21 +4698,21 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@types/estree': 1.0.0
+      '@types/estree': 1.0.1
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 3.20.2
+      rollup: 3.21.3
     dev: false
 
-  /@rushstack/node-core-library@3.55.2(@types/node@18.15.11):
-    resolution: {integrity: sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==}
+  /@rushstack/node-core-library@3.58.0(@types/node@18.16.3):
+    resolution: {integrity: sha512-DHAZ3LTOEq2/EGURznpTJDnB3SNE2CKMDXuviQ6afhru6RykE3QoqXkeyjbpLb5ib5cpIRCPE/wykNe0xmQj3w==}
     peerDependencies:
       '@types/node': '*'
     peerDependenciesMeta:
       '@types/node':
         optional: true
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       colors: 1.2.5
       fs-extra: 7.0.1
       import-lazy: 4.0.0
@@ -4991,6 +4763,7 @@ packages:
   /@sindresorhus/is@4.6.0:
     resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==}
     engines: {node: '>=10'}
+    dev: false
 
   /@sindresorhus/is@5.3.0:
     resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==}
@@ -5051,9 +4824,9 @@ packages:
       lodash.values: 4.3.0
       object-hash: 3.0.0
       packageurl-js: 1.0.1
-      semver: 7.3.8
+      semver: 7.5.0
       tslib: 2.5.0
-    dev: true
+    dev: false
 
   /@snyk/graphlib@2.1.9-patch.3:
     resolution: {integrity: sha512-bBY9b9ulfLj0v2Eer0yFYa3syVeIxVKl2EpxSrsVeT4mjA0CltZyHsF0JjoaGXP27nItTdJS5uVsj1NA+3aE+Q==}
@@ -5073,14 +4846,14 @@ packages:
       lodash.transform: 4.6.0
       lodash.union: 4.6.0
       lodash.values: 4.3.0
-    dev: true
+    dev: false
 
   /@sqltools/formatter@1.2.5:
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
     dev: false
 
-  /@storybook/addon-actions@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-rcj39u9MrmzsrDWYt1zsoVxrogZ1Amrv9xkEofEY/QKUr2R3xpHhTALveY9BKIlG1GoE8zLlLoP2k4nz3sNNwQ==}
+  /@storybook/addon-actions@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-WxsnSjAvdf6NhUfTqcwV+FJmsJV56gh2cY4QnGfqfwO5zoBWTUYnhz57TgxSMhJY0kspyX9Q1Kc//r1d5lt1qA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5090,14 +4863,14 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       dequal: 2.0.3
       lodash: 4.17.21
       polished: 4.2.2
@@ -5107,11 +4880,11 @@ packages:
       react-inspector: 6.0.1(react@18.2.0)
       telejson: 7.0.4
       ts-dedent: 2.2.0
-      uuid-browser: 3.1.0
+      uuid: 9.0.0
     dev: true
 
-  /@storybook/addon-backgrounds@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-yRNHQ4PPRJ+HIORQPhDGxn5xolw1xW0ByQZoNRpMD+AMEyfUNFdWbCsRQAOWjNhawxVMHM7EeA2Exrb41zhEjA==}
+  /@storybook/addon-backgrounds@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-DhT32K1+ti7MXY9oqt36b9jlg7iY68IP0ZQbR3gjShcsIXZpFqh18TQo0vwDY1ldqnBvkTk6Jd5vcxA8tfyshw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5121,22 +4894,22 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-controls@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-dMpRtj5cmfC9vEMve5ncvbWCEC+WD9YuzJ+grdc48E/Hd//p+O2FE6klSkrz5FAjrc+rHINixdyssekpEL6nYQ==}
+  /@storybook/addon-controls@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-/QEzleKoWRQ3i7KB32QvqDGcGMw4kG2BxEf0d+ymxd2SjoeL6kX2eHE0b4OxFPXiWUyTfXBFwmcI2Re3fRUJnQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5146,15 +4919,15 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.0.2
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.0.2
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/blocks': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.0.7
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 7.0.7
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       lodash: 4.17.21
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5163,33 +4936,29 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-docs@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-q3rDWoZEym6Lkmhqc/HBNfLDAmTY8l0WINGUZo/nF98eP5iu4B7Nk7V6BRGYGQt6Y6ZyIQ8WKH0e/eJww2zIog==}
+  /@storybook/addon-docs@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-5PT7aiTD6QPH+4CZLcv4PiUgWucD9JNGHVMRbQMEyFW6qbs87dHmu1m1uXIvx3BF5h3mTo4FHNAf8IQIq5HH9w==}
     peerDependencies:
-      '@storybook/mdx1-csf': '>=1.0.0-0'
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@storybook/mdx1-csf':
-        optional: true
     dependencies:
       '@babel/core': 7.21.3
       '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.3)
       '@jest/transform': 29.5.0
       '@mdx-js/react': 2.3.0(react@18.2.0)
-      '@storybook/blocks': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/csf-plugin': 7.0.2
-      '@storybook/csf-tools': 7.0.2
+      '@storybook/blocks': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/csf-plugin': 7.0.7
+      '@storybook/csf-tools': 7.0.7
       '@storybook/global': 5.0.0
       '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.0.2
-      '@storybook/postinstall': 7.0.2
-      '@storybook/preview-api': 7.0.2
-      '@storybook/react-dom-shim': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/node-logger': 7.0.7
+      '@storybook/postinstall': 7.0.7
+      '@storybook/preview-api': 7.0.7
+      '@storybook/react-dom-shim': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       fs-extra: 11.1.0
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5200,43 +4969,42 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-essentials@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-LAsWsXa/Pp2B4Ve2WVgc990FtsiHpFDRsq7S3V7xRrZP8DYRbtJIVdszPMDS5uKC+yzbswFEXz08lqbGvq8zgQ==}
+  /@storybook/addon-essentials@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-uNx0BvN1XP7cNnk/L4oiFQlEB/KABqOeIyI8/mhfIyTvvwo9uAYIQAyiwWuz9MFmofCNm7CgLNOUaEwNDkM4CA==}
     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.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-backgrounds': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-controls': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-docs': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-highlight': 7.0.2
-      '@storybook/addon-measure': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-outline': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-toolbars': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-viewport': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.0.2
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.0.2
-      '@storybook/preview-api': 7.0.2
+      '@storybook/addon-actions': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-backgrounds': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-controls': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-docs': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-highlight': 7.0.7
+      '@storybook/addon-measure': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-outline': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-toolbars': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-viewport': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.0.7
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 7.0.7
+      '@storybook/preview-api': 7.0.7
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     transitivePeerDependencies:
-      - '@storybook/mdx1-csf'
       - supports-color
     dev: true
 
-  /@storybook/addon-highlight@7.0.2:
-    resolution: {integrity: sha512-9BkL1OOanguuy73S6nLK0isUb045tOkFONd/PQldOJ0PV3agCvKxKHyzlBz7Hsba8KZhY5jQs+nVW2NiREyGYg==}
+  /@storybook/addon-highlight@7.0.7:
+    resolution: {integrity: sha512-expme2GzzCXX7/lL7UjCDi1Tfj+4LeNsAdWiurVLH7glK7yKPPeXXkIldbLP/XjJv4NKlqCwnNRHQx0vDLlE6g==}
     dependencies:
-      '@storybook/core-events': 7.0.2
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.2
+      '@storybook/preview-api': 7.0.7
     dev: true
 
-  /@storybook/addon-interactions@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-vPWnyGND4s9nVp+U21N/jE00dCRsHcKU68SoL4OiIZioTTRbLvrTG9eAdBkZXsVPpFHq8gndma3nXfplOSSckg==}
+  /@storybook/addon-interactions@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-jBl6O5sSbix0X1G9dFuWvvu4qefgLP9dAB/utVdDadZxlbPfa5B2C2q2YIqjcKZoX8DS8Fh8SUhlX1mdW5tu5w==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5246,16 +5014,16 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.0.2
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.0.7
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/instrumenter': 7.0.2
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/instrumenter': 7.0.7
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       jest-mock: 27.5.1
       polished: 4.2.2
       react: 18.2.0
@@ -5265,8 +5033,8 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-links@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-lPtfy2MqrcI9YjupBM2eRKGPdFKVPCz7WgO/JQQakGugORJTEGCyJrNJNtWY9jDenv8ynLZ40OxtPBZi54Sr6Q==}
+  /@storybook/addon-links@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-DEjDxjHb3mT8Sdnx4In5Ev9gJ/XdjlHOq4iuy0wnMyrCV4wnzTQnIeSCx8nkrXFb314zc33JPnCcrb5pQoD5GQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5276,22 +5044,22 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-events': 7.0.7
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/router': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       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.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-cf/d5MXpHAjyUiDIVfc8pLn79CPHgnryDmNNlSiP2zEFKcivrRWiu8Rmrad8pGqLkuAh+PXLKCGn9uiqDvg7QQ==}
+  /@storybook/addon-measure@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-lb4wEIvIVF+ePx1sC+n9rDI0+49sRa6MWbcvZ+BhbAoCeGcX7uACQFdW6HyXolmBuZASsTnzVQ4KqzzvY1dSWw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5301,19 +5069,19 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/types': 7.0.7
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/addon-outline@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-thVISO4NM22xlETisBvAPvz2yFD3qLGOjgzBmj8l8r9Rv0IEdwdPrwm5j0WTv8OtbhC4A8lPpvMsn5FhY5mDXg==}
+  /@storybook/addon-outline@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-AxbNZ4N1fXBTeMYM9tFudfW+Gzq7UikCjPxn5ax3Pde+zZjaEMppUxv5EMz4g5GIJupLYRmKH5pN0YcYoRLY6w==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5323,20 +5091,20 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/types': 7.0.7
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-storysource@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-0F47HIc97bNKSDTNnUy0M4ch/tNziWwnw+EDRCNoE6kOMJ9uF6caiTk4QCahCRoA/iNbBWCn91mnU4ZRp22Wiw==}
+  /@storybook/addon-storysource@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-gRdnWN8H5BhQF3rkcfjEIzEsZ/kdlgjiA6YA3GRKlcRCkzWHHXtHDdT3BSUFPb/8mT8DftFZNP2PKWeKd1rZvQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5346,13 +5114,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/router': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/source-loader': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/source-loader': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
       estraverse: 5.3.0
       prop-types: 15.8.1
       react: 18.2.0
@@ -5360,8 +5128,8 @@ packages:
       react-syntax-highlighter: 15.5.0(react@18.2.0)
     dev: true
 
-  /@storybook/addon-toolbars@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-tAxZ2+nUYsJdT1sx3BrmoMAZFM19+OzWJY6qSnbEq5zoRgvGZaXGR6tLMKydDoHQBU9Ta9YHGo7N7u7h1C23yg==}
+  /@storybook/addon-toolbars@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-/NkYHhU1VAz5lXjWuV8+ADWB84HzktvZv4jfiKX7Zzu6JVzrBu7FotQSWh3pDqqVwCB50RClUGtcHmSSac9CAQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5371,17 +5139,17 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/addon-viewport@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-TaHJWIIazPM/TerRbka9RqjMPNpwaRsGRdVRBtVoVosy1FzsEjAdQSO7RBMe4G03m5CacSqdsDiJCblI2AXaew==}
+  /@storybook/addon-viewport@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-znqhd8JFEFoXcAdwYhz1CwrCpVAzhuSyUVBUNDsDs+mgBEfGth4D4abIdWWGcfP6+CmI5ebFHtk443cExZebag==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5391,49 +5159,49 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
       memoizerific: 1.11.3
       prop-types: 15.8.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/addons@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-2+amBRcmJ5kD2ZNeftiyp9Or9Kd7oVjwycZZX5SBB4EFQvRNKyM0w4GVgwbeJAdx5uuw7tJFtQgnakb39mxAgA==}
+  /@storybook/addons@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-it8NWXsdm3dhjc237d9jj7dGJf6eHDfuDv12nirV64J1dzWrnW+lONeZMPMgxxdLlgYfxH52fLgjcw/dAC/E+Q==}
     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.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/types': 7.0.7
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/blocks@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-JzHmU8jZLzeQ6bunzci8j/2Ji18GBTyhrPFLk5RjEbMNGWpGjvER/yR127tZOdbPguVNr4iVbRfGzd1wGHlrzA==}
+  /@storybook/blocks@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-ehR0hAFWNHHqmrmbwYPKhLpgbIBKtyMbeoGClTRSnrVBGONciYJdmxegkCTReUklCY+HBJjtlwNowT+7+5sSaw==}
     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.0.2
-      '@storybook/client-logger': 7.0.2
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
+      '@storybook/channels': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
       '@storybook/csf': 0.1.0
-      '@storybook/docs-tools': 7.0.2
+      '@storybook/docs-tools': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       '@types/lodash': 4.14.191
       color-convert: 2.0.1
       dequal: 2.0.3
@@ -5451,19 +5219,19 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-manager@7.0.2:
-    resolution: {integrity: sha512-Oej/n8D7eaWgmWF7nN2hXLRM53lcYOdh6umSN8Mh/LcYUfxB+dvUBFzUjoLE0xjhW6xRinrKrENT5LcP/f/HBQ==}
+  /@storybook/builder-manager@7.0.7:
+    resolution: {integrity: sha512-VI/0iEjAlzQDt1yKu8GXugNIz7t46IHIKgMNmltQ05KPypMgInUoMmbfP5AYOVddjLdSqjMLO7EK58pBLOInpw==}
     dependencies:
       '@fal-works/esbuild-plugin-global-externals': 2.1.2
-      '@storybook/core-common': 7.0.2
-      '@storybook/manager': 7.0.2
-      '@storybook/node-logger': 7.0.2
+      '@storybook/core-common': 7.0.7
+      '@storybook/manager': 7.0.7
+      '@storybook/node-logger': 7.0.7
       '@types/ejs': 3.1.2
       '@types/find-cache-dir': 3.2.1
-      '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.15)
+      '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.18)
       browser-assert: 1.2.1
       ejs: 3.1.8
-      esbuild: 0.17.15
+      esbuild: 0.17.18
       esbuild-plugin-alias: 0.2.1
       express: 4.18.2
       find-cache-dir: 3.3.2
@@ -5474,34 +5242,31 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-vite@7.0.2(typescript@5.0.3)(vite@4.2.1):
-    resolution: {integrity: sha512-G6CD2Gf2zwzRslvNvqgz4FeADVEA9XA4Mw6+NM6Twc+Wy/Ah482dvHS9ApSgirtGyBKjOfdHn1xQT4Z+kzbJnw==}
+  /@storybook/builder-vite@7.0.7(typescript@5.0.4)(vite@4.3.4):
+    resolution: {integrity: sha512-2wL6fsFWzij+R155urOLc7EjZtlVWf4FLfaSlLGAuZwRQU40N04YdMaHMp9tjd9Vdr5fxEDwTB51PnVWJMlsEw==}
     peerDependencies:
       '@preact/preset-vite': '*'
-      '@storybook/mdx1-csf': '>=1.0.0-next.1'
       typescript: '>= 4.3.x'
       vite: ^3.0.0 || ^4.0.0
       vite-plugin-glimmerx: '*'
     peerDependenciesMeta:
       '@preact/preset-vite':
         optional: true
-      '@storybook/mdx1-csf':
-        optional: true
       typescript:
         optional: true
       vite-plugin-glimmerx:
         optional: true
     dependencies:
-      '@storybook/channel-postmessage': 7.0.2
-      '@storybook/channel-websocket': 7.0.2
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-common': 7.0.2
-      '@storybook/csf-plugin': 7.0.2
+      '@storybook/channel-postmessage': 7.0.7
+      '@storybook/channel-websocket': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-common': 7.0.7
+      '@storybook/csf-plugin': 7.0.7
       '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.0.2
-      '@storybook/preview': 7.0.2
-      '@storybook/preview-api': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/node-logger': 7.0.7
+      '@storybook/preview': 7.0.7
+      '@storybook/preview-api': 7.0.7
+      '@storybook/types': 7.0.7
       browser-assert: 1.2.1
       es-module-lexer: 0.9.3
       express: 4.18.2
@@ -5511,66 +5276,81 @@ packages:
       magic-string: 0.27.0
       remark-external-links: 8.0.0
       remark-slug: 6.1.0
-      rollup: 3.20.2
-      typescript: 5.0.3
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      rollup: 3.21.3
+      typescript: 5.0.4
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/channel-postmessage@7.0.0:
-    resolution: {integrity: sha512-Sy3oHL/xDRjUiHnM0ncnkbOE5pK3O72MjOoiLJX4FCI90w03KM4+F/N0eU2cXl6yXHuCyI5eJisEzQxTNsaJiw==}
+  /@storybook/channel-postmessage@7.0.2:
+    resolution: {integrity: sha512-SZ/KqnZcx10W9hJbrzBKcP9dmgaeTaXugUhcgw1IkmjKWdsKazqFZCPwQWZZKAmhO4wYbyYOhkz3wfSIeB4mFw==}
     dependencies:
-      '@storybook/channels': 7.0.0
-      '@storybook/client-logger': 7.0.0
-      '@storybook/core-events': 7.0.0
+      '@storybook/channels': 7.0.2
+      '@storybook/client-logger': 7.0.2
+      '@storybook/core-events': 7.0.2
       '@storybook/global': 5.0.0
       qs: 6.11.1
       telejson: 7.0.4
     dev: true
 
-  /@storybook/channel-postmessage@7.0.2:
-    resolution: {integrity: sha512-SZ/KqnZcx10W9hJbrzBKcP9dmgaeTaXugUhcgw1IkmjKWdsKazqFZCPwQWZZKAmhO4wYbyYOhkz3wfSIeB4mFw==}
+  /@storybook/channel-postmessage@7.0.6:
+    resolution: {integrity: sha512-xBsh/+85GS4bJ08r7z1iRn26EI6hGmMgNpjpFztRigMhsq5SkD9FJb+Nh9bbaHm+yPOCqJcaHQ2aQpuJNT8dHA==}
     dependencies:
-      '@storybook/channels': 7.0.2
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-events': 7.0.2
+      '@storybook/channels': 7.0.6
+      '@storybook/client-logger': 7.0.6
+      '@storybook/core-events': 7.0.6
       '@storybook/global': 5.0.0
       qs: 6.11.1
       telejson: 7.0.4
     dev: true
 
-  /@storybook/channel-websocket@7.0.2:
-    resolution: {integrity: sha512-YU3lFId6Nsi75ddA+3qfbnLfNUPswboYyx+SALhaLuXqz7zqfzX4ezMgxeS/h0gRlUJ7nf2/yJ5qie/kZaizjw==}
+  /@storybook/channel-postmessage@7.0.7:
+    resolution: {integrity: sha512-XMtYfcaE0UoY/V7K1cTu9PcWETD4iyWb/Yswc4F9VrPw0Ui4UwGS1j4iaAu8DC06yyoJs4XvxYFBMlCQmKja6A==}
     dependencies:
-      '@storybook/channels': 7.0.2
-      '@storybook/client-logger': 7.0.2
+      '@storybook/channels': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-events': 7.0.7
       '@storybook/global': 5.0.0
+      qs: 6.11.1
       telejson: 7.0.4
     dev: true
 
-  /@storybook/channels@7.0.0:
-    resolution: {integrity: sha512-adPIkvL4q37dGTWCpSzV8ETLdkxsg7BAgzeT9pustZJjRIZqAHGUAm7krDtGT7jbV4dU0Zw0VpUrnmyfxIkOKQ==}
+  /@storybook/channel-websocket@7.0.7:
+    resolution: {integrity: sha512-KDbLiQts4/dCow3qk5WJSPA6SlaX3iP9RhF0Fjj03hoG2TRskrvo+AkUiJr8gF6dpkPndfuCYUCRsO2Ml8B+AA==}
+    dependencies:
+      '@storybook/channels': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/global': 5.0.0
+      telejson: 7.0.4
     dev: true
 
   /@storybook/channels@7.0.2:
     resolution: {integrity: sha512-qkI8mFy9c8mxN2f01etayKhCaauL6RAsxRzbX1/pKj6UqhHWqqUbtHwymrv4hG5qDYjV1e9pd7ae5eNF8Kui0g==}
     dev: true
 
-  /@storybook/cli@7.0.2:
-    resolution: {integrity: sha512-xMM2QdXNGg09wuXzAGroKrbsnaHSFPmtmefX1XGALhHuKVwxOoC2apWMpek6gY/9vh5EIRTog2Dvfd2BzNrT6Q==}
+  /@storybook/channels@7.0.6:
+    resolution: {integrity: sha512-+34cVmrXZ3lb1s5tDK+OWd5HLtEPSUMas0VKFJ0k9LBpFlVl9aiCZBJRvSYmWL7beauUfa+HSmJgjlD6228ChQ==}
+    dev: true
+
+  /@storybook/channels@7.0.7:
+    resolution: {integrity: sha512-Om4ovBLNw8pVrBu83MpOKgAuGO9Dpr1Coh2qp8t64WRPkejX1mxOY9IgH723//zH3igx8LCkf9rvBvcrsyaScQ==}
+    dev: true
+
+  /@storybook/cli@7.0.7:
+    resolution: {integrity: sha512-koTkWr7wlaHF14T5moRP/tYM44+Jf4GEzQ/rqx/Jfn7EbNlVUOibdLJj4JnseMGRc7ZP6tKYku2n+B8g7hJX4w==}
     hasBin: true
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/preset-env': 7.20.2(@babel/core@7.21.3)
+      '@babel/preset-env': 7.21.4(@babel/core@7.21.3)
       '@ndelangen/get-tarball': 3.0.7
-      '@storybook/codemod': 7.0.2
-      '@storybook/core-common': 7.0.2
-      '@storybook/core-server': 7.0.2
-      '@storybook/csf-tools': 7.0.2
-      '@storybook/node-logger': 7.0.2
-      '@storybook/telemetry': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/codemod': 7.0.7
+      '@storybook/core-common': 7.0.7
+      '@storybook/core-server': 7.0.7
+      '@storybook/csf-tools': 7.0.7
+      '@storybook/node-logger': 7.0.7
+      '@storybook/telemetry': 7.0.7
+      '@storybook/types': 7.0.7
       '@types/semver': 7.3.13
       boxen: 5.1.2
       chalk: 4.1.2
@@ -5586,13 +5366,13 @@ packages:
       get-port: 5.1.1
       giget: 1.1.2
       globby: 11.1.0
-      jscodeshift: 0.14.0(@babel/preset-env@7.20.2)
+      jscodeshift: 0.14.0(@babel/preset-env@7.21.4)
       leven: 3.1.0
-      prettier: 2.8.7
+      prettier: 2.8.8
       prompts: 2.4.2
       puppeteer-core: 2.1.1
       read-pkg-up: 7.0.1
-      semver: 7.3.8
+      semver: 7.5.0
       shelljs: 0.8.5
       simple-update-notifier: 1.1.0
       strip-json-comments: 3.1.1
@@ -5606,49 +5386,55 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@storybook/client-logger@7.0.0:
-    resolution: {integrity: sha512-wRZZiPta37DFc8SVZ8Q3ZqyTrs5qgO6bcCuVDRLQAcO0Oz4xKEVPEVfVVxSPZU/+p2ypqdBBCP2pdL/Jy86AJg==}
+  /@storybook/client-logger@7.0.2:
+    resolution: {integrity: sha512-rv7W2BhzIQHbFpUM5/CP/acS6T5lTmaxT0MbZ9n+9h++9QQU/cFOdkZgSUbLVAb1AeUGoLsk0HYzcqPpV35Xsw==}
     dependencies:
       '@storybook/global': 5.0.0
     dev: true
 
-  /@storybook/client-logger@7.0.2:
-    resolution: {integrity: sha512-rv7W2BhzIQHbFpUM5/CP/acS6T5lTmaxT0MbZ9n+9h++9QQU/cFOdkZgSUbLVAb1AeUGoLsk0HYzcqPpV35Xsw==}
+  /@storybook/client-logger@7.0.6:
+    resolution: {integrity: sha512-TC/E5BBkY+WNldNw5p5Ffr9x4UgMe48GmC50ikBpQFk6og1B7XpFGMMbj40EBB0R5cpZkQNEVQh4OvunEygNzg==}
     dependencies:
       '@storybook/global': 5.0.0
     dev: true
 
-  /@storybook/codemod@7.0.2:
-    resolution: {integrity: sha512-D9PdByxJlFiaDJcLkM+RN1DHCj4VfQIlSZkADOcNtI4o9H064oiMloWDGZiR1i1FCYMSXuWmW6tMsuCVebA+Nw==}
+  /@storybook/client-logger@7.0.7:
+    resolution: {integrity: sha512-EclHjDs5HwHMKB4X2orn/KKA0DTIDmp4AXAUJGRfxb5ArpKEb7tXLHsgrRBlaoz1j5LAwKTmEyZOONh9G3etjg==}
+    dependencies:
+      '@storybook/global': 5.0.0
+    dev: true
+
+  /@storybook/codemod@7.0.7:
+    resolution: {integrity: sha512-VlkDlkvfbzLe+NOmzs5zGrGb4jnaeAFZqpvIkXxevr6aGcOwgeelNv8gTmgBAcy+xbGW4Pp0XA2BlMweIvKEKA==}
     dependencies:
       '@babel/core': 7.21.3
       '@babel/preset-env': 7.21.4(@babel/core@7.21.3)
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
       '@storybook/csf': 0.1.0
-      '@storybook/csf-tools': 7.0.2
-      '@storybook/node-logger': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/csf-tools': 7.0.7
+      '@storybook/node-logger': 7.0.7
+      '@storybook/types': 7.0.7
       cross-spawn: 7.0.3
       globby: 11.1.0
       jscodeshift: 0.14.0(@babel/preset-env@7.21.4)
       lodash: 4.17.21
-      prettier: 2.8.7
+      prettier: 2.8.8
       recast: 0.23.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/components@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Ee9pY6WlpricPUdYiyR0Ov8zgHkUt541yl1CZ6Ytaom2TA12cAnRjKewbLAgVPPhIE1LsMRhOPFYql0JMtnN4Q==}
+  /@storybook/components@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-6PLs9LMkBuhH/w4bSJ72tYgICMbOOIHuoB/fQdVlzhsdnXL2fM/v4RVW2N7v+Oz3lYXp/JtV8V9Ub8h6eDQKXg==}
     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.0.2
+      '@storybook/client-logger': 7.0.7
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5656,23 +5442,23 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/core-client@7.0.2:
-    resolution: {integrity: sha512-tr6Uv41YD2O0xiUrtgujiY1QxuznhbyUI0BRsSh49e8cx3QoW7FgPy7IVZHgb17DXKZ/wY/hgdyTTB87H6IbLA==}
+  /@storybook/core-client@7.0.7:
+    resolution: {integrity: sha512-eydcpR28qV3A3BwR5V6wsixoI1BRLA0SzFiwH/1ajrgX13inv+gV97gHv47Ojf/+YAZ3HqdVaUKFsUfMKwKieA==}
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/preview-api': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/preview-api': 7.0.7
     dev: true
 
-  /@storybook/core-common@7.0.2:
-    resolution: {integrity: sha512-DayFPTCj695tnEKLuDlogclBim8mzdrbj9U1xzFm23BUReheGSGdLl2zrb3mP1l9Zj4xJ/Ctst1KN9SFbW84vw==}
+  /@storybook/core-common@7.0.7:
+    resolution: {integrity: sha512-c8T24wex9bnCYdZVZFNX4VV+wfhrp47OLzVONZDqxMhq6G//Bgv5zH4Awcx5UfWf/05VcP7KGF1VKj8ebRyEEA==}
     dependencies:
-      '@storybook/node-logger': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/node-logger': 7.0.7
+      '@storybook/types': 7.0.7
       '@types/node': 16.18.16
       '@types/pretty-hrtime': 1.0.1
       chalk: 4.1.2
-      esbuild: 0.17.15
-      esbuild-register: 3.4.2(esbuild@0.17.15)
+      esbuild: 0.17.18
+      esbuild-register: 3.4.2(esbuild@0.17.18)
       file-system-cache: 2.0.2
       find-up: 5.0.0
       fs-extra: 11.1.0
@@ -5689,31 +5475,35 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/core-events@7.0.0:
-    resolution: {integrity: sha512-pxzNmgEI1p90bHyAYABHDDtB2XM5pffq6CqIHboK6aSCux7Cdc16IjOYq6BJIhCKaaI+qQHaFLR4JfaFAsxwQQ==}
-    dev: true
-
   /@storybook/core-events@7.0.2:
     resolution: {integrity: sha512-1DCHCwHRL3+rlvnVVc/BCfReP31XaT2WYgcLeGTmkX1E43Po1MkgcM7PnJPSaa9POvSqZ+6YLZv5Bs1SXbufow==}
     dev: true
 
-  /@storybook/core-server@7.0.2:
-    resolution: {integrity: sha512-7ipGws8YffVaiwkc+D0+MfZc/Sy52aKenG3nDJdK4Ajmp5LPAlelb/sxIhfRvoHDbDsy2FQNz++Mb55Yh03KkA==}
+  /@storybook/core-events@7.0.6:
+    resolution: {integrity: sha512-kGrtjlYtjd4iTVk+Phb4CymZaVkB+MGscKAgcO8gfgJ/Q/gq8HQLVZSIzeoCDcDSHOGlBzbg2WVtdHIHhCKlOQ==}
+    dev: true
+
+  /@storybook/core-events@7.0.7:
+    resolution: {integrity: sha512-XNsR2RgaL2vBwuqsu+KA1DzGmB1UFfrAhpxhmyWTKDCniwtTLlaXgfKbqwcrOrPu/o1YswgIup/9UHepRHaf4A==}
+    dev: true
+
+  /@storybook/core-server@7.0.7:
+    resolution: {integrity: sha512-PB4zoClH7aKG4XeJhxx43iK9n/C9gctXubNN5DSN6thPm4UITOas+/q4N7AHbCPyRbcMyoW7M31KtpzZu4Fjew==}
     dependencies:
       '@aw-web-design/x-default-browser': 1.4.88
       '@discoveryjs/json-ext': 0.5.7
-      '@storybook/builder-manager': 7.0.2
-      '@storybook/core-common': 7.0.2
-      '@storybook/core-events': 7.0.2
+      '@storybook/builder-manager': 7.0.7
+      '@storybook/core-common': 7.0.7
+      '@storybook/core-events': 7.0.7
       '@storybook/csf': 0.1.0
-      '@storybook/csf-tools': 7.0.2
+      '@storybook/csf-tools': 7.0.7
       '@storybook/docs-mdx': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/manager': 7.0.2
-      '@storybook/node-logger': 7.0.2
-      '@storybook/preview-api': 7.0.2
-      '@storybook/telemetry': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/manager': 7.0.7
+      '@storybook/node-logger': 7.0.7
+      '@storybook/preview-api': 7.0.7
+      '@storybook/telemetry': 7.0.7
+      '@storybook/types': 7.0.7
       '@types/detect-port': 1.3.2
       '@types/node': 16.18.16
       '@types/node-fetch': 2.6.2
@@ -5735,7 +5525,7 @@ packages:
       pretty-hrtime: 1.0.3
       prompts: 2.4.2
       read-pkg-up: 7.0.1
-      semver: 7.3.8
+      semver: 7.5.0
       serve-favicon: 2.5.0
       telejson: 7.0.4
       ts-dedent: 2.2.0
@@ -5749,24 +5539,24 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@storybook/csf-plugin@7.0.2:
-    resolution: {integrity: sha512-aGuo+G6G5IwSGkmc+OUA796sOfvJMaQj8QS/Zh5F0nL4ZlQvghHpXON8cRHHvmXHQqUo07KLiy7CZh2I2oq4iQ==}
+  /@storybook/csf-plugin@7.0.7:
+    resolution: {integrity: sha512-uhf2g077gXA6ZEMXIPQ0RnX+IoOTBJbj+6+VQfT7K5tvJeop1z0Fvk0FoknNXcUe7aUA0nzA/cUQ1v4vXqbY3Q==}
     dependencies:
-      '@storybook/csf-tools': 7.0.2
+      '@storybook/csf-tools': 7.0.7
       unplugin: 0.10.2
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/csf-tools@7.0.2:
-    resolution: {integrity: sha512-sOp355yQSpYiMqNSopmFYWZkPPRJdGgy4tpxGGLxpOZMygK3j1wQ/WQtl2Z0h61KP0S0dl6hrs0pHQz3A/eVrw==}
+  /@storybook/csf-tools@7.0.7:
+    resolution: {integrity: sha512-KbO5K2RS0oFm94eR49bAPvoyXY3Q6+ozvBek/F05RP7iAV790icQc59Xci9YDM1ONgb3afS+gSJGFBsE0h4pmg==}
     dependencies:
       '@babel/generator': 7.21.3
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
       '@storybook/csf': 0.1.0
-      '@storybook/types': 7.0.2
+      '@storybook/types': 7.0.7
       fs-extra: 11.1.0
       recast: 0.23.1
       ts-dedent: 2.2.0
@@ -5774,12 +5564,6 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/csf@0.0.2-next.11:
-    resolution: {integrity: sha512-xGt0YSVxZb43sKmEf1GIQD8xEbo+c+S6khDEL7Qu/pYA0gh5z3WUuhOlovnelYj/YJod+XRsfVvk23AaRfUJ4Q==}
-    dependencies:
-      type-fest: 2.19.0
-    dev: true
-
   /@storybook/csf@0.1.0:
     resolution: {integrity: sha512-uk+jMXCZ8t38jSTHk2o5btI+aV2Ksbvl6DoOv3r6VaCM1KZqeuMwtwywIQdflkA8/6q/dKT8z8L+g8hC4GC3VQ==}
     dependencies:
@@ -5790,13 +5574,13 @@ packages:
     resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==}
     dev: true
 
-  /@storybook/docs-tools@7.0.2:
-    resolution: {integrity: sha512-w4D5BURrYjLbLGG9VKAaKU2dSdukszxRE3HWkJyhQU9R1JHvS3n8ntcMqYPqRfoHCOeBLBxP0edDYcAfzGNDYQ==}
+  /@storybook/docs-tools@7.0.7:
+    resolution: {integrity: sha512-VB4Qa33DYNxXALWcXyDid246r9Q6SGM+Q+pGWOuEJsxRxDmrUspXHaHG0CO1NIjMWfbqpOoz61vquZO0GZoAAg==}
     dependencies:
       '@babel/core': 7.21.3
-      '@storybook/core-common': 7.0.2
-      '@storybook/preview-api': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/core-common': 7.0.7
+      '@storybook/preview-api': 7.0.7
+      '@storybook/types': 7.0.7
       '@types/doctrine': 0.0.3
       doctrine: 3.0.0
       lodash: 4.17.21
@@ -5807,23 +5591,13 @@ packages:
   /@storybook/expect@27.5.2-0:
     resolution: {integrity: sha512-cP99mhWN/JeCp7VSIiymvj5tmuMY050iFohvp8Zq+kewKsBSZ6/qpTJAGCCZk6pneTcp4S0Fm5BSqyxzbyJ3gw==}
     dependencies:
-      '@types/jest': 29.5.0
+      '@types/jest': 29.5.1
     dev: true
 
   /@storybook/global@5.0.0:
     resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
     dev: true
 
-  /@storybook/instrumenter@7.0.0:
-    resolution: {integrity: sha512-A7jBrV7VM3OxRgall8rpjagy3VC78A/OV1g1aYVVLpAF/+Odj+MeHHF179+fR6JBLnBgukNfsG7/ZHHGs0gL5Q==}
-    dependencies:
-      '@storybook/channels': 7.0.0
-      '@storybook/client-logger': 7.0.0
-      '@storybook/core-events': 7.0.0
-      '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.0
-    dev: true
-
   /@storybook/instrumenter@7.0.2:
     resolution: {integrity: sha512-zr9/fuaYtGVUtcL8XgjA4Iq5jtzdcqQyOSH4XLXtz6JtSad3lkRagbJo2Vzbw7dO/4vzjfTMxEzvWjUuPxLOhA==}
     dependencies:
@@ -5834,50 +5608,70 @@ packages:
       '@storybook/preview-api': 7.0.2
     dev: true
 
+  /@storybook/instrumenter@7.0.6:
+    resolution: {integrity: sha512-JUcDas1cYCE+ZMVOw5CKc5g6PxDe3HH+IGdh/W9wL5vmdOUvAs858m7NLxkjkQGufof+Ohbmf/Yz5gyXaZ5+Yg==}
+    dependencies:
+      '@storybook/channels': 7.0.6
+      '@storybook/client-logger': 7.0.6
+      '@storybook/core-events': 7.0.6
+      '@storybook/global': 5.0.0
+      '@storybook/preview-api': 7.0.6
+    dev: true
+
+  /@storybook/instrumenter@7.0.7:
+    resolution: {integrity: sha512-0zE5lM3laKvCT4GW/XKKw8kakvI4catqK8PObZolRhfxbtGufW4VJZ2E8vXLtgA/+K3zikypjuWE6d45NLbh9w==}
+    dependencies:
+      '@storybook/channels': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-events': 7.0.7
+      '@storybook/global': 5.0.0
+      '@storybook/preview-api': 7.0.7
+    dev: true
+
   /@storybook/jest@0.1.0:
     resolution: {integrity: sha512-TmybnEXlv5Fu2/Hq4nRj7alS9mw4CasLR0RDwaAzS+Vpvu1TC4+j9rh+b1BHtmWebbJh0JMT6mgzPqOyJdgtQA==}
     dependencies:
       '@storybook/expect': 27.5.2-0
-      '@storybook/instrumenter': 7.0.0
+      '@storybook/instrumenter': 7.0.2
       '@testing-library/jest-dom': 5.16.5
       jest-mock: 27.5.1
     dev: true
 
-  /@storybook/manager-api@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-PbLj9Rc5uCMPfMdaXv1wE3koA3+d0rmZ3BJI8jeq+mfZEvpvfI4OOpRioT1q04CkkVomFOVFTyO0Q/o6Rb5N7g==}
+  /@storybook/manager-api@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-QTd/P72peAhofKqK+8yzIO9iWAEfPn8WUGGveV2KGaTlSlgbr87RLHEKilcXMZcYhBWC9izFRmjKum9ROdskrQ==}
     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.0.2
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-events': 7.0.2
+      '@storybook/channels': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-events': 7.0.7
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/router': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/router': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       dequal: 2.0.3
       lodash: 4.17.21
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      semver: 7.3.8
+      semver: 7.5.0
       store2: 2.14.2
       telejson: 7.0.4
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/manager@7.0.2:
-    resolution: {integrity: sha512-jsFsFKG0rPNYfuRm/WSXGMBy8vnALyFWU330ObDmfU0JID3SeLlVqAOZT1GlwI6vupYpWodsN6qPZKRmC8onRw==}
+  /@storybook/manager@7.0.7:
+    resolution: {integrity: sha512-FhquwUpUOHsjZROf6E6kzUnJ6EmMeJ9b+HYg6yYPyIMYVMjAhnkRKbIj0phGx2lhgKFlmxik+3pgchK5SLdcZA==}
     dev: true
 
   /@storybook/mdx2-csf@1.0.0:
     resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==}
     dev: true
 
-  /@storybook/node-logger@7.0.2:
-    resolution: {integrity: sha512-UENpXxB1yDqP7JXaODJo+pbGt5y3NFBNurBr4+pI4bMAC4ARjpgRE4wp6fxUKFPu9MAR10oCdcLEHkaVUAjYRg==}
+  /@storybook/node-logger@7.0.7:
+    resolution: {integrity: sha512-5Y4LLgKeCStq1ktCKZ5eNPzQQSQ+CYZAlkEdzQ3Pp//0KXaZvVxEvGtaYhAymP2HatLpI8Oneo4lHrJioRfgww==}
     dependencies:
       '@types/npmlog': 4.1.4
       chalk: 4.1.2
@@ -5885,20 +5679,20 @@ packages:
       pretty-hrtime: 1.0.3
     dev: true
 
-  /@storybook/postinstall@7.0.2:
-    resolution: {integrity: sha512-Hhiu3+N3ZDcbrhOCBJTDJbn/mC4l0v3ziyAP3yalq/2ZR9R5kfsEHHakKmswsKKV+ey0gNGijFTy3soU5oSs+A==}
+  /@storybook/postinstall@7.0.7:
+    resolution: {integrity: sha512-APcZ2KaR7z1aJje3pID4Ywmt1/aVcP3Sc4ltzNdH9mCkEsuq0fZHHQrYSa9Ya1IPRmSeLZ5/23q1iyqmGU3zoQ==}
     dev: true
 
-  /@storybook/preview-api@7.0.0:
-    resolution: {integrity: sha512-Q0IYYH1gOmx42ClYlQfQPjuERBWM3Ey+3DFsLQaraKXDdgZ9wN7jPNuS7wxuUNylT0oa/3WjxT7qNfiGw8JtBw==}
+  /@storybook/preview-api@7.0.2:
+    resolution: {integrity: sha512-QAlJM/r92+dQe/kB7MTTR9b/1mt9UJjxNjazGdEWipA/nw23kOF3o/hBcvKwBYkit4zGYsX70H+vuzW8hCo/lA==}
     dependencies:
-      '@storybook/channel-postmessage': 7.0.0
-      '@storybook/channels': 7.0.0
-      '@storybook/client-logger': 7.0.0
-      '@storybook/core-events': 7.0.0
-      '@storybook/csf': 0.0.2-next.11
+      '@storybook/channel-postmessage': 7.0.2
+      '@storybook/channels': 7.0.2
+      '@storybook/client-logger': 7.0.2
+      '@storybook/core-events': 7.0.2
+      '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/types': 7.0.0
+      '@storybook/types': 7.0.2
       '@types/qs': 6.9.7
       dequal: 2.0.3
       lodash: 4.17.21
@@ -5909,16 +5703,16 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/preview-api@7.0.2:
-    resolution: {integrity: sha512-QAlJM/r92+dQe/kB7MTTR9b/1mt9UJjxNjazGdEWipA/nw23kOF3o/hBcvKwBYkit4zGYsX70H+vuzW8hCo/lA==}
+  /@storybook/preview-api@7.0.6:
+    resolution: {integrity: sha512-uNsedNyiEccBV2EDUC/xcKTbmiNCYuVHbgOoWTmBz0ZqFo9bX0jxkpyYWHEhJM79qqVqmrpiQ5jbS8QKn8TIxQ==}
     dependencies:
-      '@storybook/channel-postmessage': 7.0.2
-      '@storybook/channels': 7.0.2
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-events': 7.0.2
+      '@storybook/channel-postmessage': 7.0.6
+      '@storybook/channels': 7.0.6
+      '@storybook/client-logger': 7.0.6
+      '@storybook/core-events': 7.0.6
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/types': 7.0.2
+      '@storybook/types': 7.0.6
       '@types/qs': 6.9.7
       dequal: 2.0.3
       lodash: 4.17.21
@@ -5929,12 +5723,32 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/preview@7.0.2:
-    resolution: {integrity: sha512-U7MZkDT9bBq7HggLAXmTO9gI4eqhYs26fZS0L6iTE/PCX4Wg2TJBJSq2X8jhDXRqJFOt8SrQ756+V5Vtwrh4Og==}
+  /@storybook/preview-api@7.0.7:
+    resolution: {integrity: sha512-R5pmGTodpu6hbwEg2RM2ulWtW3d426YzsisHrZJ+FT9lecWauN1y9xHCz7HdNzEFhT8r4YOa24L9ZS3mosZ7hA==}
+    dependencies:
+      '@storybook/channel-postmessage': 7.0.7
+      '@storybook/channels': 7.0.7
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-events': 7.0.7
+      '@storybook/csf': 0.1.0
+      '@storybook/global': 5.0.0
+      '@storybook/types': 7.0.7
+      '@types/qs': 6.9.7
+      dequal: 2.0.3
+      lodash: 4.17.21
+      memoizerific: 1.11.3
+      qs: 6.11.1
+      synchronous-promise: 2.0.17
+      ts-dedent: 2.2.0
+      util-deprecate: 1.0.2
+    dev: true
+
+  /@storybook/preview@7.0.7:
+    resolution: {integrity: sha512-uL3ZcFao6UvxiSxCIcXKFakxEr9Nn0lvu0zzC2yQCVepzA7a+GDr1cK5VbZ6Mez38CnOvBmb5pkCbgRqSf/oug==}
     dev: true
 
-  /@storybook/react-dom-shim@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-fMl0aV7mJ3wyQKvt6z+rZuiIiSd9YinS77IJ1ETHqVZ4SxWriOS0GFKP6sZflrlpShoZBh+zl1lDPG7ZZdrQGw==}
+  /@storybook/react-dom-shim@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-INGwFeu9M+RzpvktSKuwy8Rk/70mXGqxxsb9lPtq7phmETvfpNX7GnLJqiVazTaQiB1DkB0iAPUsK2MNbBu+Kw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5943,35 +5757,34 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/react-vite@7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1):
-    resolution: {integrity: sha512-1bDrmGo6imxBzZKJJ+SEHPuDn474JY3Yatm0cPaNVtlYhbnbiTPa3PxhI4U3233l4Qsc6DXNLKvi++j/knXDCw==}
+  /@storybook/react-vite@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4):
+    resolution: {integrity: sha512-RuWfP/kiLpuHdcF9dWUUp9SOGMmO0FJ0HGV5yAOhGmi8KmTzvc8zjC+hJjj+sSgn2n71BO8pG/zqGl16FwfwVQ==}
     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.0.3)(vite@4.2.1)
+      '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.4)(vite@4.3.4)
       '@rollup/pluginutils': 4.2.1
-      '@storybook/builder-vite': 7.0.2(typescript@5.0.3)(vite@4.2.1)
-      '@storybook/react': 7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)
-      '@vitejs/plugin-react': 3.1.0(vite@4.2.1)
+      '@storybook/builder-vite': 7.0.7(typescript@5.0.4)(vite@4.3.4)
+      '@storybook/react': 7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)
+      '@vitejs/plugin-react': 3.1.0(vite@4.3.4)
       ast-types: 0.14.2
       magic-string: 0.27.0
       react: 18.2.0
       react-docgen: 6.0.0-alpha.3
       react-dom: 18.2.0(react@18.2.0)
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
     transitivePeerDependencies:
       - '@preact/preset-vite'
-      - '@storybook/mdx1-csf'
       - supports-color
       - typescript
       - vite-plugin-glimmerx
     dev: true
 
-  /@storybook/react@7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3):
-    resolution: {integrity: sha512-2P7Oju1XKWMyn75dO0vjL4gthzBL/lLiCBRyAHKXZJ1H2eNdWjXkOOtH1HxnbRcXjWSU4tW96dqKY8m0iR9zAA==}
+  /@storybook/react@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-eEsIfAGumzo7KRi/WKFpn/PGFhwLv72oiEM/8l5MMX/6poIkiekunqJLfx2BoL4cCtiS4g7OYzOdWjN01DwVCg==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5981,13 +5794,13 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-client': 7.0.2
-      '@storybook/docs-tools': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-client': 7.0.7
+      '@storybook/docs-tools': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.2
-      '@storybook/react-dom-shim': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/preview-api': 7.0.7
+      '@storybook/react-dom-shim': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       '@types/escodegen': 0.0.6
       '@types/estree': 0.0.51
       '@types/node': 16.18.16
@@ -6003,119 +5816,127 @@ packages:
       react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0)
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      typescript: 5.0.3
+      typescript: 5.0.4
       util-deprecate: 1.0.2
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/router@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-ZB2vucfayZUrMLBlXju4v6CNOQQb0YKDLw5RoojdBxOsUFtnp5UiPOE+I8PQR63EBwnRjozeibV1XSM+GlQb5w==}
+  /@storybook/router@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-/lM8/NHQKeshfnC3ayFuO8Y9TCSHnCAPRhIsVxvanBzcj+ILbCIyZ+TspvB3hT4MbX/Ez+JR8VrMbjXIGwmH8w==}
     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.0.2
+      '@storybook/client-logger': 7.0.7
       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.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-YIEluNmRHxDZByhwnwTwo+NXKuPrxlCDjdtkMHqvlLQW60KV4l8zaBaBP4//Jq4ycYI1Qt85UKzeZPYzJVT50g==}
+  /@storybook/source-loader@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-vAURuD0WmafHe8XGbkwcOn1dCLcU4ociL5xakCw0upgW0cUczp4BMRfwmP0Az5YDhfoQRjBVmUaFfjONhTDawg==}
     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.0.2
+      '@storybook/types': 7.0.7
       estraverse: 5.3.0
       lodash: 4.17.21
-      prettier: 2.8.7
+      prettier: 2.8.8
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/telemetry@7.0.2:
-    resolution: {integrity: sha512-s2PIwI9nVYQBf3h40EFHLynYUfdqzRJMXyaCWJdVQuvdQfRkAn3CLXaubK+VdjC869z3ZfW20EMu3Mbgzcc0HA==}
+  /@storybook/telemetry@7.0.7:
+    resolution: {integrity: sha512-Ka6pwWr3sWs3A/6WQ0wsoSYzXx3Mhr7eByNZZKuuCu9jnw3I8AbIOqQX2iOVzaQBLZsvXEeqvYY8iZ+GuRbbGQ==}
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/core-common': 7.0.2
+      '@storybook/client-logger': 7.0.7
+      '@storybook/core-common': 7.0.7
       chalk: 4.1.2
       detect-package-manager: 2.0.1
       fetch-retry: 5.0.4
       fs-extra: 11.1.0
       isomorphic-unfetch: 3.1.0
-      nanoid: 3.3.4
+      nanoid: 3.3.6
       read-pkg-up: 7.0.1
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  /@storybook/testing-library@0.0.14-next.1:
-    resolution: {integrity: sha512-1CAl40IKIhcPaCC4pYCG0b9IiYNymktfV/jTrX7ctquRY3akaN7f4A1SippVHosksft0M+rQTFE0ccfWW581fw==}
+  /@storybook/testing-library@0.1.0:
+    resolution: {integrity: sha512-g947f4LJZw3IluBhysMKLJXByAFiSxnGuooENqU+ZPt/GTrz1I9GDBlhmoTJahuFkVbwHvziAl/8riY2Re921g==}
     dependencies:
-      '@storybook/client-logger': 7.0.2
-      '@storybook/instrumenter': 7.0.2
+      '@storybook/client-logger': 7.0.6
+      '@storybook/instrumenter': 7.0.6
       '@testing-library/dom': 8.20.0
       '@testing-library/user-event': 13.5.0(@testing-library/dom@8.20.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/theming@7.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-c9sE+QAZNbopPvLiJ6BMxBERfTaq1ATyIri97FBvTucuSotNXw7X5q+ip5/nrCOPZuvK2f5wF4DRyD2HnB/rIQ==}
+  /@storybook/theming@7.0.7(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-InTZe+Sgco1NsxgiG+cyUKWQe3GsjlIyU/o5qDdtOTXcZ64HzyBuAZlAequSddqfDeMDqxRFPc2w1J28MAUHxA==}
     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.0.2
+      '@storybook/client-logger': 7.0.7
       '@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.0.0:
-    resolution: {integrity: sha512-eCMW/xTVMswgD/58itibw8s8f2hZ7tciT3saRdGCymg9tPUhMC/9eGIIUSr/C+xfnCJEZm6J6DgEUo1xlifonw==}
+  /@storybook/types@7.0.2:
+    resolution: {integrity: sha512-0OCt/kAexa8MCcljxA+yZxGMn0n2U2Ync0KxotItqNbKBKVkaLQUls0+IXTWSCpC/QJvNZ049jxUHHanNi/96w==}
     dependencies:
-      '@storybook/channels': 7.0.0
+      '@storybook/channels': 7.0.2
       '@types/babel__core': 7.20.0
       '@types/express': 4.17.17
       file-system-cache: 2.0.2
     dev: true
 
-  /@storybook/types@7.0.2:
-    resolution: {integrity: sha512-0OCt/kAexa8MCcljxA+yZxGMn0n2U2Ync0KxotItqNbKBKVkaLQUls0+IXTWSCpC/QJvNZ049jxUHHanNi/96w==}
+  /@storybook/types@7.0.6:
+    resolution: {integrity: sha512-dFASQxzvldU2Nx/eJG+oL4wCchUWAKOmOSYJYhKgtGpx99oXOiWUyC0SgCpTveBJ7AppoiseyasQ9Gd/Ccycdw==}
     dependencies:
-      '@storybook/channels': 7.0.2
+      '@storybook/channels': 7.0.6
       '@types/babel__core': 7.20.0
       '@types/express': 4.17.17
       file-system-cache: 2.0.2
     dev: true
 
-  /@storybook/vue3-vite@7.0.2(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.3)(vite@4.2.1)(vue@3.2.47):
-    resolution: {integrity: sha512-lmxnHA9wHkgbNq+oW6dVnXbe9QOFjOz4Ejkl1AAjjg0blJ+VGautVa3mSeYM99szx5EigSfQjFAkv/TAJVC80Q==}
+  /@storybook/types@7.0.7:
+    resolution: {integrity: sha512-v9piuwp8FvTiHXIOOi5lEyTEJKhnbcbhVxgJ3VFhhXYFd0DTz6Bst0XIIgkgs21ITb3xhkfPbCRUueMcbXO1MA==}
+    dependencies:
+      '@storybook/channels': 7.0.7
+      '@types/babel__core': 7.20.0
+      '@types/express': 4.17.17
+      file-system-cache: 2.0.2
+    dev: true
+
+  /@storybook/vue3-vite@7.0.7(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.4)(vue@3.2.47):
+    resolution: {integrity: sha512-ePAOd5TBYq6WJTHgSP6B/k3PXzv8JU5sm956FMmpnDXvttZGKlp+a1qjabA8IbKD8SKfmjQmV7719Uh/ZPyQ6Q==}
     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.0.2(typescript@5.0.3)(vite@4.2.1)
-      '@storybook/core-server': 7.0.2
-      '@storybook/vue3': 7.0.2(vue@3.2.47)
-      '@vitejs/plugin-vue': 4.1.0(vite@4.2.1)(vue@3.2.47)
+      '@storybook/builder-vite': 7.0.7(typescript@5.0.4)(vite@4.3.4)
+      '@storybook/core-server': 7.0.7
+      '@storybook/vue3': 7.0.7(vue@3.2.47)
+      '@vitejs/plugin-vue': 4.2.1(vite@4.3.4)(vue@3.2.47)
       magic-string: 0.27.0
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
       vue-docgen-api: 4.64.1(vue@3.2.47)
     transitivePeerDependencies:
       - '@preact/preset-vite'
-      - '@storybook/mdx1-csf'
       - bufferutil
       - encoding
       - supports-color
@@ -6125,17 +5946,17 @@ packages:
       - vue
     dev: true
 
-  /@storybook/vue3@7.0.2(vue@3.2.47):
-    resolution: {integrity: sha512-0KDwhDYg9TQTB9awEcm8IeBMkxD6ZQFHuSHfE7TEWSx83H9pGb6CkVkAgjZlmfMiSY6gI160G4mJt2/tgTxbiA==}
+  /@storybook/vue3@7.0.7(vue@3.2.47):
+    resolution: {integrity: sha512-SPZYm46zVkL9julVrQsUpPY02M/XzaDVsODlC75dXgL1jBskmbgW6WkjFEOoNr1RtEblb+t+pqxLoK4/LA09qw==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       vue: ^3.0.0
     dependencies:
-      '@storybook/core-client': 7.0.2
-      '@storybook/docs-tools': 7.0.2
+      '@storybook/core-client': 7.0.7
+      '@storybook/docs-tools': 7.0.7
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.2
-      '@storybook/types': 7.0.2
+      '@storybook/preview-api': 7.0.7
+      '@storybook/types': 7.0.7
       ts-dedent: 2.2.0
       type-fest: 2.19.0
       vue: 3.2.47
@@ -6143,7 +5964,7 @@ packages:
       - supports-color
     dev: true
 
-  /@swc/cli@0.1.62(@swc/core@1.3.46)(chokidar@3.5.3):
+  /@swc/cli@0.1.62(@swc/core@1.3.56)(chokidar@3.5.3):
     resolution: {integrity: sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==}
     engines: {node: '>= 12.13'}
     hasBin: true
@@ -6155,11 +5976,11 @@ packages:
         optional: true
     dependencies:
       '@mole-inc/bin-wrapper': 8.0.1
-      '@swc/core': 1.3.46(@swc/helpers@0.5.0)
+      '@swc/core': 1.3.56
       chokidar: 3.5.3
       commander: 7.2.0
       fast-glob: 3.2.12
-      semver: 7.3.8
+      semver: 7.5.0
       slash: 3.0.0
       source-map: 0.7.4
     dev: false
@@ -6175,119 +5996,115 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-arm64@1.3.46:
-    resolution: {integrity: sha512-kY4ASe7SsntDw2B1T70H9K1CFmK8POi+LyIpeCyC96EB9wbH2Sax+ploBB/wZALbYzr/dMJzOCU8QXzdmVS4Rg==}
+  /@swc/core-darwin-arm64@1.3.56:
+    resolution: {integrity: sha512-DZcu7BzDaLEdWHabz9DRTP0yEBLqkrWmskFcD5BX0lGAvoIvE4duMnAqi5F2B3X7630QioHRCYFoRw2WkeE3Cw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@swc/core-darwin-x64@1.3.46:
-    resolution: {integrity: sha512-kE3PMk8xW+2BZ3oZiTxxsUU/GzrGwM+qS4frOBz9TYHZe+W1dTtj4F9vBit4PFJ+tv4O6DPt9neGobzdq0UmRw==}
+  /@swc/core-darwin-x64@1.3.56:
+    resolution: {integrity: sha512-VH5saqYFasdRXJy6RAT+MXm0+IjkMZvOkohJwUei+oA65cKJofQwrJ1jZro8yOJFYvUSI3jgNRGsdBkmo/4hMw==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@swc/core-linux-arm-gnueabihf@1.3.46:
-    resolution: {integrity: sha512-7TbiUr9MYxT+mC7sVrayag/isFoaZUG/ogkEK8B/ouA1pnIYqWh3N5ifqCzfcSRiOURt+vVqPyoO1puSiNzVuQ==}
+  /@swc/core-linux-arm-gnueabihf@1.3.56:
+    resolution: {integrity: sha512-LWwPo6NnJkH01+ukqvkoNIOpMdw+Zundm4vBeicwyVrkP+mC3kwVfi03TUFpQUz3kRKdw/QEnxGTj+MouCPbtw==}
     engines: {node: '>=10'}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@swc/core-linux-arm64-gnu@1.3.46:
-    resolution: {integrity: sha512-Ycw4LU/wsUK9R+Y/2qFOPQseZDfM5D5gbWGrrYj5RoTm57FbnUsSsO26QeZxUNvams1oAQDkZDuerCc9qBRzIQ==}
+  /@swc/core-linux-arm64-gnu@1.3.56:
+    resolution: {integrity: sha512-GzsUy/4egJ4cMlxbM+Ub7AMi5CKAc+pxBxrh8MUPQbyStW8jGgnQsJouTnGy0LHawtdEnsCOl6PcO6OgvktXuQ==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@swc/core-linux-arm64-musl@1.3.46:
-    resolution: {integrity: sha512-cBclyr6IW1PBr8l9D4FkebgbqlkiIYnSJCbY84J/6PfTzQlD6w9a1TAoYxdGZpJ7SGHdmB0oDiZS1rhxCSCV/Q==}
+  /@swc/core-linux-arm64-musl@1.3.56:
+    resolution: {integrity: sha512-9gxL09BIiAv8zY0DjfnFf19bo8+P4T9tdhzPwcm+1yPJcY5yr1+YFWLNFzz01agtOj6VlZ2/wUJTaOfdjjtc+A==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@swc/core-linux-x64-gnu@1.3.46:
-    resolution: {integrity: sha512-amqMhTA2CXB6t11hVAZSSPKq4DZ9/sWbW3wYYQHxzqrMJML0726OJs4pt0XnlU7FzdP/9M9j2B/gWCRaCMxXVA==}
+  /@swc/core-linux-x64-gnu@1.3.56:
+    resolution: {integrity: sha512-n0ORNknl50vMRkll3BDO1E4WOqY6iISlPV1ZQCRLWQ6YQ2q8/WAryBxc2OAybcGHBUFkxyACpJukeU1QZ/9tNw==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@swc/core-linux-x64-musl@1.3.46:
-    resolution: {integrity: sha512-WOQZTIkJ9khIj5Z2unf6OTrWV9k8br+HZ93RvnamEmJBlLPUuT9IjB+agNhjaDgOpz9/ZldSGqV7vzl5FGQl1Q==}
+  /@swc/core-linux-x64-musl@1.3.56:
+    resolution: {integrity: sha512-r+D34WLAOAlJtfw1gaVWpHRwCncU9nzW9i7w9kSw4HpWYnHJOz54jLGSEmNsrhdTCz1VK2ar+V2ktFUsrlGlDA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@swc/core-win32-arm64-msvc@1.3.46:
-    resolution: {integrity: sha512-4JSREbqaTRQ6QO0EeoiB6G5vuFT8zI8aTOLu5At7Cvlw+X7bOGNO+wJ3Tqw7O+68OL+0bPHzHGTXKL9kUccY1A==}
+  /@swc/core-win32-arm64-msvc@1.3.56:
+    resolution: {integrity: sha512-29Yt75Is6X24z3x8h/xZC1HnDPkPpyLH9mDQiM6Cuc0I9mVr1XSriPEUB2N/awf5IE4SA8c+3IVq1DtKWbkJIw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@swc/core-win32-ia32-msvc@1.3.46:
-    resolution: {integrity: sha512-kC8dIDzcArm1e85yHJsEZFxcNq5NztLkrqkP1nVOQ+9QXD9DKhjbZtWy2gnpclinii6KEGng8SieWiJiOA0CBQ==}
+  /@swc/core-win32-ia32-msvc@1.3.56:
+    resolution: {integrity: sha512-mplp0zbYDrcHtfvkniXlXdB04e2qIjz2Gq/XHKr4Rnc6xVORJjjXF91IemXKpavx2oZYJws+LNJL7UFQ8jyCdQ==}
     engines: {node: '>=10'}
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@swc/core-win32-x64-msvc@1.3.46:
-    resolution: {integrity: sha512-rrSAfq+DvpJioBxUsnuH+sKl0eXid1DwkwNzkVGHEreN9GoP7GospWtFq7VDcO6DrS/s3HtR4/TzoIYFEBCRIg==}
+  /@swc/core-win32-x64-msvc@1.3.56:
+    resolution: {integrity: sha512-zp8MBnrw/bjdLenO/ifYzHrImSjKunqL0C2IF4LXYNRfcbYFh2NwobsVQMZ20IT0474lKRdlP8Oxdt+bHuXrzA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@swc/core@1.3.46(@swc/helpers@0.5.0):
-    resolution: {integrity: sha512-WxzgJMWUBVJ95HsvEqlWzM3Qxp2FQrPa4QdAkQQuuvCMnfdctGUbhX/c3LiSRlWrl2LIkYAi4bLansTOol4QcQ==}
+  /@swc/core@1.3.56:
+    resolution: {integrity: sha512-yz/EeXT+PMZucUNrYceRUaTfuNS4IIu5EDZSOlvCEvm4jAmZi7CYH1B/kvzEzoAOzr7zkQiDPNJftcQXLkjbjA==}
     engines: {node: '>=10'}
     requiresBuild: true
     peerDependencies:
       '@swc/helpers': ^0.5.0
-    dependencies:
-      '@swc/helpers': 0.5.0
+    peerDependenciesMeta:
+      '@swc/helpers':
+        optional: true
     optionalDependencies:
-      '@swc/core-darwin-arm64': 1.3.46
-      '@swc/core-darwin-x64': 1.3.46
-      '@swc/core-linux-arm-gnueabihf': 1.3.46
-      '@swc/core-linux-arm64-gnu': 1.3.46
-      '@swc/core-linux-arm64-musl': 1.3.46
-      '@swc/core-linux-x64-gnu': 1.3.46
-      '@swc/core-linux-x64-musl': 1.3.46
-      '@swc/core-win32-arm64-msvc': 1.3.46
-      '@swc/core-win32-ia32-msvc': 1.3.46
-      '@swc/core-win32-x64-msvc': 1.3.46
-
-  /@swc/helpers@0.5.0:
-    resolution: {integrity: sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg==}
-    dependencies:
-      tslib: 2.5.0
-
-  /@swc/jest@0.2.24(@swc/core@1.3.46):
-    resolution: {integrity: sha512-fwgxQbM1wXzyKzl1+IW0aGrRvAA8k0Y3NxFhKigbPjOJ4mCKnWEcNX9HQS3gshflcxq8YKhadabGUVfdwjCr6Q==}
+      '@swc/core-darwin-arm64': 1.3.56
+      '@swc/core-darwin-x64': 1.3.56
+      '@swc/core-linux-arm-gnueabihf': 1.3.56
+      '@swc/core-linux-arm64-gnu': 1.3.56
+      '@swc/core-linux-arm64-musl': 1.3.56
+      '@swc/core-linux-x64-gnu': 1.3.56
+      '@swc/core-linux-x64-musl': 1.3.56
+      '@swc/core-win32-arm64-msvc': 1.3.56
+      '@swc/core-win32-ia32-msvc': 1.3.56
+      '@swc/core-win32-x64-msvc': 1.3.56
+
+  /@swc/jest@0.2.26(@swc/core@1.3.56):
+    resolution: {integrity: sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==}
     engines: {npm: '>= 7.0.0'}
     peerDependencies:
       '@swc/core': '*'
     dependencies:
       '@jest/create-cache-key-function': 27.5.1
-      '@swc/core': 1.3.46(@swc/helpers@0.5.0)
+      '@swc/core': 1.3.56
       jsonc-parser: 3.2.0
     dev: true
 
@@ -6297,8 +6114,8 @@ packages:
     dev: false
     optional: true
 
-  /@syuilo/aiscript@0.13.1:
-    resolution: {integrity: sha512-WJduqlsm7pq8r1oYbPSAf5cqR6Pve6ipGoPUUSgti51dMytMnxE2dn7d7i8759RF3yftyoclZbL/DC7IjPsTng==}
+  /@syuilo/aiscript@0.13.2:
+    resolution: {integrity: sha512-1aqQSH6U+vV01UDUotXUEjIwJKcZZPASJyIJ9msxXRpSInPGJJ/q1kGkZMgSpVhzYFT7/QBxo0UC1ZVEOsrDTw==}
     dependencies:
       autobind-decorator: 2.4.0
       seedrandom: 3.0.5
@@ -6311,6 +6128,7 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       defer-to-connect: 2.0.1
+    dev: false
 
   /@szmarczak/http-timer@5.0.1:
     resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==}
@@ -6318,59 +6136,58 @@ packages:
     dependencies:
       defer-to-connect: 2.0.1
 
-  /@tabler/icons-webfont@2.12.0:
-    resolution: {integrity: sha512-5I4jub4zwBo4WdnZhRRmfjPutY7JL/ZbdGjH6MJ56x9RuYeJu62geJIAr8eHDdOSTWguUzZI3lQMygXoz63grw==}
+  /@tabler/icons-webfont@2.17.0:
+    resolution: {integrity: sha512-jgRZWiWCaG++jFTIU/dbOT+JmSgoFlALwBUUS31mt1b5py7B0YWelnfxf5s3ctE+0dlnoIS+r7rDOeDSAWx8SA==}
     dependencies:
-      '@tabler/icons': 2.12.0
+      '@tabler/icons': 2.17.0
     dev: false
 
-  /@tabler/icons@2.12.0:
-    resolution: {integrity: sha512-PiYD2zUOewNAtfJKU9tBZfyv3NeFlTlUe5LSGTTplvBcM0iuHrNVnd30+HIcUGJQhi3Uf0Ng9FRi/kPqm/q6ZA==}
+  /@tabler/icons@2.17.0:
+    resolution: {integrity: sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==}
     dev: false
 
-  /@tensorflow/tfjs-backend-cpu@4.2.0(@tensorflow/tfjs-core@4.2.0):
-    resolution: {integrity: sha512-8HWg9J69m0Ovc6w8TVhhixMOcwA3t/NPXLblOA/sgJ+/JD5gsbpLWJk4QISQyb1RnpSVzw6PX3BSMTJU7hWVOg==}
+  /@tensorflow/tfjs-backend-cpu@4.4.0(@tensorflow/tfjs-core@4.4.0):
+    resolution: {integrity: sha512-d4eln500/qNym78z9IrUUzF0ITBoJGLrxV8xd92kLVoXhg35Mm+zqUXShjFcrH8joOHOFuST0qZ0TbDDqcPzPA==}
     engines: {yarn: '>= 1.3.2'}
     peerDependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
     dependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
       '@types/seedrandom': 2.4.30
       seedrandom: 3.0.5
     dev: false
 
-  /@tensorflow/tfjs-backend-webgl@4.2.0(@tensorflow/tfjs-core@4.2.0):
-    resolution: {integrity: sha512-Qvf+hD5pSh+xi48kChSGzcDKJemkc4EKfoVVjuxl4k25ZUPwuEd7zZUAtinkLu1dzgHNyvePZY8k+9rVm59HJA==}
+  /@tensorflow/tfjs-backend-webgl@4.4.0(@tensorflow/tfjs-core@4.4.0):
+    resolution: {integrity: sha512-TzQKvfAPgGt9cMG+5bVoTckoG1xr/PVJM/uODkPvzcMqi3j97kuWDXwkYJIgXldStmfiKkU7f5CmyD3Cq3E6BA==}
     engines: {yarn: '>= 1.3.2'}
     peerDependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
     dependencies:
-      '@tensorflow/tfjs-backend-cpu': 4.2.0(@tensorflow/tfjs-core@4.2.0)
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-backend-cpu': 4.4.0(@tensorflow/tfjs-core@4.4.0)
+      '@tensorflow/tfjs-core': 4.4.0
       '@types/offscreencanvas': 2019.3.0
       '@types/seedrandom': 2.4.30
       '@types/webgl-ext': 0.0.30
       seedrandom: 3.0.5
     dev: false
 
-  /@tensorflow/tfjs-converter@4.2.0(@tensorflow/tfjs-core@4.2.0):
-    resolution: {integrity: sha512-m+E2KJM6yGQdi8ElzWpChdD/JaqhWMCi9yK70v/ndkOaCL2q2UN48nYP2T5S15vkDvMIgzAQyZfh7hxQsMuvRQ==}
+  /@tensorflow/tfjs-converter@4.4.0(@tensorflow/tfjs-core@4.4.0):
+    resolution: {integrity: sha512-JUjpRStrAuw37tgPd5UENu0UjQVuJT09yF7KpOur4BriJ0uQqrbEZHMPHmvUtr5nYzkqlXJTuXIyxvEY/olNpg==}
     peerDependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
     dependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
     dev: false
 
-  /@tensorflow/tfjs-core@4.2.0:
-    resolution: {integrity: sha512-uuHkiWVC8b00ngFbHvAV7J7haRlN/9PEdeenCi0CzBjgKd7aN25wPWaoN0TSQcU+GT4FJ8mofMZ9VBYZ/s/WLg==}
+  /@tensorflow/tfjs-core@4.4.0:
+    resolution: {integrity: sha512-Anxpc7cAOA0Q7EUXdTbQKMg3reFvrdkgDlaYzH9ZfkMq2CgLV4Au6E/s6HmbYn/VrAtWy9mLY5c/lLJqh4764g==}
     engines: {yarn: '>= 1.3.2'}
-    requiresBuild: true
     dependencies:
       '@types/long': 4.0.2
       '@types/offscreencanvas': 2019.7.0
       '@types/seedrandom': 2.4.30
       '@types/webgl-ext': 0.0.30
-      '@webgpu/types': 0.1.21
+      '@webgpu/types': 0.1.30
       long: 4.0.0
       node-fetch: 2.6.7
       seedrandom: 3.0.5
@@ -6378,13 +6195,13 @@ packages:
       - encoding
     dev: false
 
-  /@tensorflow/tfjs-data@4.2.0(@tensorflow/tfjs-core@4.2.0)(seedrandom@3.0.5):
-    resolution: {integrity: sha512-11t7Q+ikseduJgkd9iSeRrtor1aA3o5PVCFhC5yYvR3JLO55ic1+4Ryo0EJfhRoismS6zBUJrpzX4K0zlLbIfw==}
+  /@tensorflow/tfjs-data@4.4.0(@tensorflow/tfjs-core@4.4.0)(seedrandom@3.0.5):
+    resolution: {integrity: sha512-aY4eq4cgrsrXeBU6ABZAAN3tV0fG4YcHd0z+cYuNXnCo+VEQLJnPmhn+xymZ4VQZQH4GXbVS4dV9pXMclFNRFw==}
     peerDependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
       seedrandom: ^3.0.5
     dependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
       '@types/node-fetch': 2.6.2
       node-fetch: 2.6.7
       seedrandom: 3.0.5
@@ -6393,21 +6210,21 @@ packages:
       - encoding
     dev: false
 
-  /@tensorflow/tfjs-layers@4.2.0(@tensorflow/tfjs-core@4.2.0):
-    resolution: {integrity: sha512-SO0KTmCFOjrW+PlP9nKYXz07XGFq6uE7am9yH2bRaRPWpEeaKT/+k0C9vFMxI/GzRwY8AK4sLe4U+jE1mhYxGw==}
+  /@tensorflow/tfjs-layers@4.4.0(@tensorflow/tfjs-core@4.4.0):
+    resolution: {integrity: sha512-OGC7shfiD9Gc698hINHK4y9slOJvu5m54tVNm4xf+WSNrw/avvgpar6yyoL5bakYIZNQvFNK75Yr8VRPR7oPeQ==}
     peerDependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
     dependencies:
-      '@tensorflow/tfjs-core': 4.2.0
+      '@tensorflow/tfjs-core': 4.4.0
     dev: false
 
-  /@tensorflow/tfjs-node@4.2.0(seedrandom@3.0.5):
-    resolution: {integrity: sha512-dkyExfqTjMVDpKLBzHH2b7JWzGK+QORtYswvoNHbeHwx7kvYYRAQeiHxp+xrgry6LdbWfZs88IWCwNGoOiZ21w==}
+  /@tensorflow/tfjs-node@4.4.0(seedrandom@3.0.5):
+    resolution: {integrity: sha512-+JSAddsupjSQUDZeb7QGOFkL3Tty3kjPHx8ethiYFzwTZJHCMvM7wZJd0Fqnjxym6A0KpsmB7SPZgwRRXVIlPA==}
     engines: {node: '>=8.11.0'}
     requiresBuild: true
     dependencies:
       '@mapbox/node-pre-gyp': 1.0.9
-      '@tensorflow/tfjs': 4.2.0(seedrandom@3.0.5)
+      '@tensorflow/tfjs': 4.4.0(seedrandom@3.0.5)
       adm-zip: 0.5.10
       google-protobuf: 3.21.2
       https-proxy-agent: 2.2.4
@@ -6421,18 +6238,19 @@ packages:
     dev: false
     optional: true
 
-  /@tensorflow/tfjs@4.2.0(seedrandom@3.0.5):
-    resolution: {integrity: sha512-iZmtyGC9IJkx+TpFnkgDol8BHv2BU3zJ01HyNcuvnm1w1EqoNe+1n8bwvLzI/sxHMcHTqzuu7VugMaphryxE+A==}
+  /@tensorflow/tfjs@4.4.0(seedrandom@3.0.5):
+    resolution: {integrity: sha512-EmCsnzdvawyk4b+4JKaLLuicHcJQRZtL1zSy9AWJLiiHTbDDseYgLxfaCEfLk8v2bUe7SBXwl3n3B7OjgvH11Q==}
+    hasBin: true
     dependencies:
-      '@tensorflow/tfjs-backend-cpu': 4.2.0(@tensorflow/tfjs-core@4.2.0)
-      '@tensorflow/tfjs-backend-webgl': 4.2.0(@tensorflow/tfjs-core@4.2.0)
-      '@tensorflow/tfjs-converter': 4.2.0(@tensorflow/tfjs-core@4.2.0)
-      '@tensorflow/tfjs-core': 4.2.0
-      '@tensorflow/tfjs-data': 4.2.0(@tensorflow/tfjs-core@4.2.0)(seedrandom@3.0.5)
-      '@tensorflow/tfjs-layers': 4.2.0(@tensorflow/tfjs-core@4.2.0)
+      '@tensorflow/tfjs-backend-cpu': 4.4.0(@tensorflow/tfjs-core@4.4.0)
+      '@tensorflow/tfjs-backend-webgl': 4.4.0(@tensorflow/tfjs-core@4.4.0)
+      '@tensorflow/tfjs-converter': 4.4.0(@tensorflow/tfjs-core@4.4.0)
+      '@tensorflow/tfjs-core': 4.4.0
+      '@tensorflow/tfjs-data': 4.4.0(@tensorflow/tfjs-core@4.4.0)(seedrandom@3.0.5)
+      '@tensorflow/tfjs-layers': 4.4.0(@tensorflow/tfjs-core@4.4.0)
       argparse: 1.0.10
       chalk: 4.1.2
-      core-js: 3.27.1
+      core-js: 3.29.1
       regenerator-runtime: 0.13.11
       yargs: 16.2.0
     transitivePeerDependencies:
@@ -6523,7 +6341,7 @@ packages:
   /@types/accepts@1.3.5:
     resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/archiver@5.3.2:
@@ -6543,8 +6361,8 @@ packages:
   /@types/babel__core@7.20.0:
     resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
     dependencies:
-      '@babel/parser': 7.21.3
-      '@babel/types': 7.21.3
+      '@babel/parser': 7.21.4
+      '@babel/types': 7.21.4
       '@types/babel__generator': 7.6.4
       '@types/babel__template': 7.4.1
       '@types/babel__traverse': 7.18.3
@@ -6553,20 +6371,20 @@ packages:
   /@types/babel__generator@7.6.4:
     resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==}
     dependencies:
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
     dev: true
 
   /@types/babel__template@7.4.1:
     resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==}
     dependencies:
-      '@babel/parser': 7.21.3
-      '@babel/types': 7.21.3
+      '@babel/parser': 7.21.4
+      '@babel/types': 7.21.4
     dev: true
 
   /@types/babel__traverse@7.18.3:
     resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==}
     dependencies:
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
     dev: true
 
   /@types/bcryptjs@2.4.2:
@@ -6577,7 +6395,7 @@ packages:
     resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/braces@3.0.1:
@@ -6597,8 +6415,9 @@ packages:
     dependencies:
       '@types/http-cache-semantics': 4.0.1
       '@types/keyv': 3.1.4
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/responselike': 1.0.0
+    dev: false
 
   /@types/cbor@6.0.0:
     resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==}
@@ -6629,7 +6448,7 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/content-disposition@0.5.5:
@@ -6664,7 +6483,7 @@ packages:
 
   /@types/emscripten@1.39.6:
     resolution: {integrity: sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==}
-    dev: true
+    dev: false
 
   /@types/escape-regexp@0.0.1:
     resolution: {integrity: sha512-ogj/ZTIdeFkiuxDwawYuZSIgC6suFGgBeZPr6Xs5lHEcvIXTjXGtH+/n8f1XhZhespaUwJ5LIGRICPji972FLw==}
@@ -6677,7 +6496,7 @@ packages:
   /@types/eslint@7.29.0:
     resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==}
     dependencies:
-      '@types/estree': 1.0.0
+      '@types/estree': 1.0.1
       '@types/json-schema': 7.0.11
     dev: true
 
@@ -6685,8 +6504,8 @@ packages:
     resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==}
     dev: true
 
-  /@types/estree@1.0.0:
-    resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==}
+  /@types/estree@1.0.1:
+    resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
 
   /@types/expect@1.20.4:
     resolution: {integrity: sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==}
@@ -6694,7 +6513,7 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
     dev: true
@@ -6715,34 +6534,34 @@ packages:
   /@types/fluent-ffmpeg@2.1.21:
     resolution: {integrity: sha512-+n3dy/Tegt6n+YwGZUiGq6i8Jrnt8+MoyPiW1L6J5EWUl7GSt18a/VyReecfCsvTTNBXNMIKOMHDstiQM8nJLA==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/glob-stream@6.1.1:
     resolution: {integrity: sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==}
     dependencies:
       '@types/glob': 8.1.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/glob@7.2.0:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/glob@8.1.0:
     resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/graceful-fs@4.1.6:
     resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/gulp-rename@2.0.1:
@@ -6769,12 +6588,6 @@ packages:
   /@types/http-cache-semantics@4.0.1:
     resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
 
-  /@types/ioredis@4.28.10:
-    resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==}
-    dependencies:
-      '@types/node': 18.15.11
-    dev: true
-
   /@types/istanbul-lib-coverage@2.0.4:
     resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
     dev: true
@@ -6791,8 +6604,8 @@ packages:
       '@types/istanbul-lib-report': 3.0.0
     dev: true
 
-  /@types/jest@29.5.0:
-    resolution: {integrity: sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==}
+  /@types/jest@29.5.1:
+    resolution: {integrity: sha512-tEuVcHrpaixS36w7hpsfLBLpjtMRJUE09/MHXn923LOVojDwyC14cWcfc0rDs0VEfUyYmt/+iX1kxxp+gZMcaQ==}
     dependencies:
       expect: 29.5.0
       pretty-format: 29.5.0
@@ -6809,7 +6622,7 @@ packages:
   /@types/jsdom@21.1.1:
     resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/tough-cookie': 4.0.2
       parse5: 7.1.2
     dev: true
@@ -6833,7 +6646,8 @@ packages:
   /@types/keyv@3.1.4:
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
+    dev: false
 
   /@types/lodash@4.14.191:
     resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
@@ -6851,8 +6665,8 @@ packages:
     resolution: {integrity: sha512-IgHxcT3RC8LzFLhKwP3gbMPeaK7BM9eBH46OdapPA7yvuIUJ8H6zHZV53J8hGZcTSnt95jANt+rTBNUUc22ACQ==}
     dev: true
 
-  /@types/micromatch@3.1.1:
-    resolution: {integrity: sha512-Wr5y4uv3r7JP4jEUqv7rZeYiMBGRHcbojDVsl11wq6gw1v/ZZQvJexd9rtvVx3EIVqw8dwtcRjSs8m2DV9qHjQ==}
+  /@types/micromatch@4.0.2:
+    resolution: {integrity: sha512-oqXqVb0ci19GtH0vOA/U2TmHTcRY9kuZl4mqUxe0QmJAlIW13kzhuK5pi1i9+ngav8FjpSb9FVS/GE00GLX1VA==}
     dependencies:
       '@types/braces': 3.0.1
     dev: true
@@ -6880,7 +6694,7 @@ packages:
   /@types/node-fetch@2.6.2:
     resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       form-data: 3.0.1
 
   /@types/node-fetch@3.0.3:
@@ -6891,7 +6705,7 @@ packages:
 
   /@types/node@13.13.52:
     resolution: {integrity: sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==}
-    dev: true
+    dev: false
 
   /@types/node@14.18.36:
     resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==}
@@ -6904,13 +6718,13 @@ packages:
   /@types/node@18.11.18:
     resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
 
-  /@types/node@18.15.11:
-    resolution: {integrity: sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==}
+  /@types/node@18.16.3:
+    resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==}
 
   /@types/nodemailer@6.4.7:
     resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/normalize-package-data@2.4.1:
@@ -6924,7 +6738,7 @@ packages:
   /@types/oauth@0.9.1:
     resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/offscreencanvas@2019.3.0:
@@ -6938,7 +6752,7 @@ packages:
   /@types/pg@8.6.6:
     resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       pg-protocol: 1.5.0
       pg-types: 2.2.0
     dev: true
@@ -6966,7 +6780,7 @@ packages:
   /@types/qrcode@1.5.0:
     resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/qs@6.9.7:
@@ -6996,7 +6810,7 @@ packages:
   /@types/readdir-glob@1.1.1:
     resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/redis@4.0.11:
@@ -7012,7 +6826,8 @@ packages:
   /@types/responselike@1.0.0:
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
+    dev: false
 
   /@types/sanitize-html@2.9.0:
     resolution: {integrity: sha512-4fP/kEcKNj2u39IzrxWYuf/FnCCwwQCpif6wwY6ROUS1EPRIfWJjGkY3HIowY1EX/VbX5e86yq8AAE7UPMgATg==}
@@ -7034,13 +6849,12 @@ packages:
 
   /@types/semver@7.3.13:
     resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
-    dev: true
 
   /@types/serve-static@1.15.1:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/serviceworker@0.0.67:
@@ -7050,13 +6864,14 @@ packages:
   /@types/set-cookie-parser@2.4.2:
     resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
-  /@types/sharp@0.31.1:
-    resolution: {integrity: sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==}
+  /@types/sharp@0.32.0:
+    resolution: {integrity: sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==}
+    deprecated: This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.
     dependencies:
-      '@types/node': 18.15.11
+      sharp: 0.32.1
     dev: true
 
   /@types/sinon@10.0.13:
@@ -7084,7 +6899,7 @@ packages:
   /@types/testing-library__jest-dom@5.14.5:
     resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==}
     dependencies:
-      '@types/jest': 29.5.0
+      '@types/jest': 29.5.1
     dev: true
 
   /@types/throttle-debounce@5.0.0:
@@ -7105,7 +6920,7 @@ packages:
 
   /@types/treeify@1.0.0:
     resolution: {integrity: sha512-ONpcZAEYlbPx4EtJwfTyCDQJGUpKf4sEcuySdCVjK5Fj/3vHp5HII1fqa1/+qrsLnpYELCQTfVW/awsGJePoIg==}
-    dev: true
+    dev: false
 
   /@types/undertaker-registry@1.0.1:
     resolution: {integrity: sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ==}
@@ -7114,7 +6929,7 @@ packages:
   /@types/undertaker@1.2.8:
     resolution: {integrity: sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/undertaker-registry': 1.0.1
       async-done: 1.3.2
     dev: true
@@ -7126,7 +6941,7 @@ packages:
   /@types/unzipper@0.10.5:
     resolution: {integrity: sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/uuid@9.0.1:
@@ -7136,14 +6951,14 @@ packages:
   /@types/vary@1.1.0:
     resolution: {integrity: sha512-LQWqrIa0dvEOOH37lGksMEXbypRLUFqu6Gx0pmX7zIUisD2I/qaVgEX/vJ/PSVSW0Hk6yz1BNkFpqg6dZm3Wug==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/vinyl-fs@2.4.12:
     resolution: {integrity: sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==}
     dependencies:
       '@types/glob-stream': 6.1.1
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       '@types/vinyl': 2.0.7
     dev: true
 
@@ -7151,12 +6966,12 @@ packages:
     resolution: {integrity: sha512-4UqPv+2567NhMQuMLdKAyK4yzrfCqwaTt6bLhHEs8PFcxbHILsrxaY63n4wgE/BRLDWDQeI+WcTmkXKExh9hQg==}
     dependencies:
       '@types/expect': 1.20.4
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
 
   /@types/web-push@3.3.2:
     resolution: {integrity: sha512-JxWGVL/m7mWTIg4mRYO+A6s0jPmBkr4iJr39DqJpRJAc+jrPiEe1/asmkwerzRon8ZZDxaZJpsxpv0Z18Wo9gw==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/webgl-ext@0.0.30:
@@ -7165,18 +6980,18 @@ packages:
 
   /@types/webpack-env@1.18.0:
     resolution: {integrity: sha512-56/MAlX5WMsPVbOg7tAxnYvNYMMWr/QJiIp6BxVSW3JJXUVzzOn64qW8TzQyMSqSUFM2+PVI4aUHcHOzIz/1tg==}
-    dev: true
+    dev: false
 
   /@types/websocket@1.0.5:
     resolution: {integrity: sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/ws@8.5.4:
     resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /@types/yargs-parser@21.0.0:
@@ -7199,12 +7014,12 @@ packages:
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
     requiresBuild: true
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
     optional: true
 
-  /@typescript-eslint/eslint-plugin@5.57.1(@typescript-eslint/parser@5.57.1)(eslint@8.37.0)(typescript@5.0.3):
-    resolution: {integrity: sha512-1MeobQkQ9tztuleT3v72XmY0XuKXVXusAhryoLuU5YZ+mXoYKZP9SQ7Flulh1NX4DTjpGTc2b/eMu4u7M7dhnQ==}
+  /@typescript-eslint/eslint-plugin@5.59.2(@typescript-eslint/parser@5.59.2)(eslint@8.39.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-yVrXupeHjRxLDcPKL10sGQ/QlVrA8J5IYOEWVqk0lJaSZP7X5DfnP7Ns3cc74/blmbipQ1htFNVGsHX6wsYm0A==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       '@typescript-eslint/parser': ^5.0.0
@@ -7215,24 +7030,24 @@ packages:
         optional: true
     dependencies:
       '@eslint-community/regexpp': 4.5.0
-      '@typescript-eslint/parser': 5.57.1(eslint@8.37.0)(typescript@5.0.3)
-      '@typescript-eslint/scope-manager': 5.57.1
-      '@typescript-eslint/type-utils': 5.57.1(eslint@8.37.0)(typescript@5.0.3)
-      '@typescript-eslint/utils': 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+      '@typescript-eslint/parser': 5.59.2(eslint@8.39.0)(typescript@5.0.4)
+      '@typescript-eslint/scope-manager': 5.59.2
+      '@typescript-eslint/type-utils': 5.59.2(eslint@8.39.0)(typescript@5.0.4)
+      '@typescript-eslint/utils': 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.37.0
+      eslint: 8.39.0
       grapheme-splitter: 1.0.4
       ignore: 5.2.4
       natural-compare-lite: 1.4.0
-      semver: 7.3.8
-      tsutils: 3.21.0(typescript@5.0.3)
-      typescript: 5.0.3
+      semver: 7.5.0
+      tsutils: 3.21.0(typescript@5.0.4)
+      typescript: 5.0.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@5.57.1(eslint@8.37.0)(typescript@5.0.3):
-    resolution: {integrity: sha512-hlA0BLeVSA/wBPKdPGxoVr9Pp6GutGoY380FEhbVi0Ph4WNe8kLvqIRx76RSQt1lynZKfrXKs0/XeEk4zZycuA==}
+  /@typescript-eslint/parser@5.59.2(eslint@8.39.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-uq0sKyw6ao1iFOZZGk9F8Nro/8+gfB5ezl1cA06SrqbgJAt0SRoFhb9pXaHvkrxUpZaoLxt8KlovHNk8Gp6/HQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
@@ -7241,26 +7056,26 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 5.57.1
-      '@typescript-eslint/types': 5.57.1
-      '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.3)
+      '@typescript-eslint/scope-manager': 5.59.2
+      '@typescript-eslint/types': 5.59.2
+      '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.0.4)
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.37.0
-      typescript: 5.0.3
+      eslint: 8.39.0
+      typescript: 5.0.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/scope-manager@5.57.1:
-    resolution: {integrity: sha512-N/RrBwEUKMIYxSKl0oDK5sFVHd6VI7p9K5MyUlVYAY6dyNb/wHUqndkTd3XhpGlXgnQsBkRZuu4f9kAHghvgPw==}
+  /@typescript-eslint/scope-manager@5.59.2:
+    resolution: {integrity: sha512-dB1v7ROySwQWKqQ8rEWcdbTsFjh2G0vn8KUyvTXdPoyzSL6lLGkiXEV5CvpJsEe9xIdKV+8Zqb7wif2issoOFA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      '@typescript-eslint/types': 5.57.1
-      '@typescript-eslint/visitor-keys': 5.57.1
+      '@typescript-eslint/types': 5.59.2
+      '@typescript-eslint/visitor-keys': 5.59.2
     dev: true
 
-  /@typescript-eslint/type-utils@5.57.1(eslint@8.37.0)(typescript@5.0.3):
-    resolution: {integrity: sha512-/RIPQyx60Pt6ga86hKXesXkJ2WOS4UemFrmmq/7eOyiYjYv/MUSHPlkhU6k9T9W1ytnTJueqASW+wOmW4KrViw==}
+  /@typescript-eslint/type-utils@5.59.2(eslint@8.39.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-b1LS2phBOsEy/T381bxkkywfQXkV1dWda/z0PhnIy3bC5+rQWQDS7fk9CSpcXBccPY27Z6vBEuaPBCKCgYezyQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: '*'
@@ -7269,23 +7084,23 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.3)
-      '@typescript-eslint/utils': 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+      '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.0.4)
+      '@typescript-eslint/utils': 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.37.0
-      tsutils: 3.21.0(typescript@5.0.3)
-      typescript: 5.0.3
+      eslint: 8.39.0
+      tsutils: 3.21.0(typescript@5.0.4)
+      typescript: 5.0.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/types@5.57.1:
-    resolution: {integrity: sha512-bSs4LOgyV3bJ08F5RDqO2KXqg3WAdwHCu06zOqcQ6vqbTJizyBhuh1o1ImC69X4bV2g1OJxbH71PJqiO7Y1RuA==}
+  /@typescript-eslint/types@5.59.2:
+    resolution: {integrity: sha512-LbJ/HqoVs2XTGq5shkiKaNTuVv5tTejdHgfdjqRUGdYhjW1crm/M7og2jhVskMt8/4wS3T1+PfFvL1K3wqYj4w==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /@typescript-eslint/typescript-estree@5.57.1(typescript@5.0.3):
-    resolution: {integrity: sha512-A2MZqD8gNT0qHKbk2wRspg7cHbCDCk2tcqt6ScCFLr5Ru8cn+TCfM786DjPhqwseiS+PrYwcXht5ztpEQ6TFTw==}
+  /@typescript-eslint/typescript-estree@5.59.2(typescript@5.0.4):
+    resolution: {integrity: sha512-+j4SmbwVmZsQ9jEyBMgpuBD0rKwi9RxRpjX71Brr73RsYnEr3Lt5QZ624Bxphp8HUkSKfqGnPJp1kA5nl0Sh7Q==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       typescript: '*'
@@ -7293,47 +7108,47 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 5.57.1
-      '@typescript-eslint/visitor-keys': 5.57.1
+      '@typescript-eslint/types': 5.59.2
+      '@typescript-eslint/visitor-keys': 5.59.2
       debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.3.8
-      tsutils: 3.21.0(typescript@5.0.3)
-      typescript: 5.0.3
+      semver: 7.5.0
+      tsutils: 3.21.0(typescript@5.0.4)
+      typescript: 5.0.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/utils@5.57.1(eslint@8.37.0)(typescript@5.0.3):
-    resolution: {integrity: sha512-kN6vzzf9NkEtawECqze6v99LtmDiUJCVpvieTFA1uL7/jDghiJGubGZ5csicYHU1Xoqb3oH/R5cN5df6W41Nfg==}
+  /@typescript-eslint/utils@5.59.2(eslint@8.39.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-kSuF6/77TZzyGPhGO4uVp+f0SBoYxCDf+lW3GKhtKru/L8k/Hd7NFQxyWUeY7Z/KGB2C6Fe3yf2vVi4V9TsCSQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.37.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.39.0)
       '@types/json-schema': 7.0.11
       '@types/semver': 7.3.13
-      '@typescript-eslint/scope-manager': 5.57.1
-      '@typescript-eslint/types': 5.57.1
-      '@typescript-eslint/typescript-estree': 5.57.1(typescript@5.0.3)
-      eslint: 8.37.0
+      '@typescript-eslint/scope-manager': 5.59.2
+      '@typescript-eslint/types': 5.59.2
+      '@typescript-eslint/typescript-estree': 5.59.2(typescript@5.0.4)
+      eslint: 8.39.0
       eslint-scope: 5.1.1
-      semver: 7.3.8
+      semver: 7.5.0
     transitivePeerDependencies:
       - supports-color
       - typescript
     dev: true
 
-  /@typescript-eslint/visitor-keys@5.57.1:
-    resolution: {integrity: sha512-RjQrAniDU0CEk5r7iphkm731zKlFiUjvcBS2yHAg8WWqFMCaCrD0rKEVOMUyMMcbGPZ0bPp56srkGWrgfZqLRA==}
+  /@typescript-eslint/visitor-keys@5.59.2:
+    resolution: {integrity: sha512-EEpsO8m3RASrKAHI9jpavNv9NlEUebV4qmF1OWxSTtKSFBpC1NCmWazDQHFivRf0O1DV11BA645yrLEVQ0/Lig==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      '@typescript-eslint/types': 5.57.1
+      '@typescript-eslint/types': 5.59.2
       eslint-visitor-keys: 3.4.0
     dev: true
 
-  /@vitejs/plugin-react@3.1.0(vite@4.2.1):
+  /@vitejs/plugin-react@3.1.0(vite@4.3.4):
     resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
@@ -7344,121 +7159,203 @@ packages:
       '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.3)
       magic-string: 0.27.0
       react-refresh: 0.14.0
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.1.0(vite@4.2.1)(vue@3.2.47):
-    resolution: {integrity: sha512-++9JOAFdcXI3lyer9UKUV4rfoQ3T1RN8yDqoCLar86s0xQct5yblxAE+yWgRnU5/0FOlVCpTZpYSBV/bGWrSrQ==}
+  /@vitejs/plugin-vue@4.2.1(vite@4.3.4)(vue@3.2.47):
+    resolution: {integrity: sha512-ZTZjzo7bmxTRTkb8GSTwkPOYDIP7pwuyV+RV53c9PYUouwcbkIZIvWvNWlX2b1dYZqtOv7D6iUAnJLVNGcLrSw==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
       vue: 3.2.47
 
-  /@vitest/coverage-c8@0.29.8(vitest@0.29.8):
-    resolution: {integrity: sha512-y+sEMQMctWokjnSqm3FCQEYFkjLrYaznsxEZHxcx8z2aftpYg3A5tvI1S5himfdEFo7o+OeHzh40bPSWZHW4oQ==}
+  /@vitest/coverage-c8@0.30.1(vitest@0.30.1):
+    resolution: {integrity: sha512-/Wa3dtSuckpdngAmiCwowaEXXgJkqPrtfvrs9HTB9QoEfNbZWPu4E4cjEn4lJZb4qcGf4fxFtUA2f9DnDNAzBA==}
     peerDependencies:
-      vitest: '>=0.29.0 <1'
+      vitest: '>=0.30.0 <1'
     dependencies:
       c8: 7.13.0
       picocolors: 1.0.0
       std-env: 3.3.2
-      vitest: 0.29.8(happy-dom@8.9.0)(sass@1.60.0)
+      vitest: 0.30.1(happy-dom@9.10.2)(sass@1.62.1)
     dev: true
 
-  /@vitest/expect@0.29.8:
-    resolution: {integrity: sha512-xlcVXn5I5oTq6NiZSY3ykyWixBxr5mG8HYtjvpgg6KaqHm0mvhX18xuwl5YGxIRNt/A5jidd7CWcNHrSvgaQqQ==}
+  /@vitest/expect@0.30.1:
+    resolution: {integrity: sha512-c3kbEtN8XXJSeN81iDGq29bUzSjQhjES2WR3aColsS4lPGbivwLtas4DNUe0jD9gg/FYGIteqOenfU95EFituw==}
     dependencies:
-      '@vitest/spy': 0.29.8
-      '@vitest/utils': 0.29.8
+      '@vitest/spy': 0.30.1
+      '@vitest/utils': 0.30.1
       chai: 4.3.7
     dev: true
 
-  /@vitest/runner@0.29.8:
-    resolution: {integrity: sha512-FzdhnRDwEr/A3Oo1jtIk/B952BBvP32n1ObMEb23oEJNO+qO5cBet6M2XWIDQmA7BDKGKvmhUf2naXyp/2JEwQ==}
+  /@vitest/runner@0.30.1:
+    resolution: {integrity: sha512-W62kT/8i0TF1UBCNMRtRMOBWJKRnNyv9RrjIgdUryEe0wNpGZvvwPDLuzYdxvgSckzjp54DSpv1xUbv4BQ0qVA==}
     dependencies:
-      '@vitest/utils': 0.29.8
+      '@vitest/utils': 0.30.1
+      concordance: 5.0.4
       p-limit: 4.0.0
       pathe: 1.1.0
     dev: true
 
-  /@vitest/spy@0.29.8:
-    resolution: {integrity: sha512-VdjBe9w34vOMl5I5mYEzNX8inTxrZ+tYUVk9jxaZJmHFwmDFC/GV3KBFTA/JKswr3XHvZL+FE/yq5EVhb6pSAw==}
+  /@vitest/snapshot@0.30.1:
+    resolution: {integrity: sha512-fJZqKrE99zo27uoZA/azgWyWbFvM1rw2APS05yB0JaLwUIg9aUtvvnBf4q7JWhEcAHmSwbrxKFgyBUga6tq9Tw==}
+    dependencies:
+      magic-string: 0.30.0
+      pathe: 1.1.0
+      pretty-format: 27.5.1
+    dev: true
+
+  /@vitest/spy@0.30.1:
+    resolution: {integrity: sha512-YfJeIf37GvTZe04ZKxzJfnNNuNSmTEGnla2OdL60C8od16f3zOfv9q9K0nNii0NfjDJRt/CVN/POuY5/zTS+BA==}
     dependencies:
-      tinyspy: 1.1.1
+      tinyspy: 2.1.0
     dev: true
 
-  /@vitest/utils@0.29.8:
-    resolution: {integrity: sha512-qGzuf3vrTbnoY+RjjVVIBYfuWMjn3UMUqyQtdGNZ6ZIIyte7B37exj6LaVkrZiUTvzSadVvO/tJm8AEgbGCBPg==}
+  /@vitest/utils@0.30.1:
+    resolution: {integrity: sha512-/c8Xv2zUVc+rnNt84QF0Y0zkfxnaGhp87K2dYJMLtLOIckPzuxLVzAtFCicGFdB4NeBHNzTRr1tNn7rCtQcWFA==}
     dependencies:
-      cli-truncate: 3.1.0
-      diff: 5.1.0
+      concordance: 5.0.4
       loupe: 2.3.6
       pretty-format: 27.5.1
     dev: true
 
-  /@volar/language-core@1.3.0-alpha.0:
-    resolution: {integrity: sha512-W3uMzecHPcbwddPu4SJpUcPakRBK/y/BP+U0U6NiPpUX1tONLC4yCawt+QBJqtgJ+sfD6ztf5PyvPL3hQRqfOA==}
+  /@volar/language-core@1.4.1:
+    resolution: {integrity: sha512-EIY+Swv+TjsWpxOxujjMf1ZXqOjg9MT2VMXZ+1dKva0wD8W0L6EtptFFcCJdBbcKmGMFkr57Qzz9VNMWhs3jXQ==}
     dependencies:
-      '@volar/source-map': 1.3.0-alpha.0
+      '@volar/source-map': 1.4.1
     dev: true
 
-  /@volar/source-map@1.3.0-alpha.0:
-    resolution: {integrity: sha512-jSdizxWFvDTvkPYZnO6ew3sBZUnS0abKCbuopkc0JrIlFbznWC/fPH3iPFIMS8/IIkRxq1Jh9VVG60SmtsdaMQ==}
+  /@volar/source-map@1.4.1:
+    resolution: {integrity: sha512-bZ46ad72dsbzuOWPUtJjBXkzSQzzSejuR3CT81+GvTEI2E994D8JPXzM3tl98zyCNnjgs4OkRyliImL1dvJ5BA==}
     dependencies:
       muggle-string: 0.2.2
     dev: true
 
-  /@volar/typescript@1.3.0-alpha.0:
-    resolution: {integrity: sha512-5UItyW2cdH2mBLu4RrECRNJRgtvvzKrSCn2y3v/D61QwIDkGx4aeil6x8RFuUL5TFtV6QvVHXnsOHxNgd+sCow==}
+  /@volar/typescript@1.4.1(typescript@5.0.4):
+    resolution: {integrity: sha512-phTy6p9yG6bgMIKQWEeDOi/aeT0njZsb1a/G1mrEuDsLmAn24Le4gDwSsGNhea6Uhu+3gdpUZn2PmZXa+WG2iQ==}
+    peerDependencies:
+      typescript: '*'
     dependencies:
-      '@volar/language-core': 1.3.0-alpha.0
+      '@volar/language-core': 1.4.1
+      typescript: 5.0.4
     dev: true
 
-  /@volar/vue-language-core@1.2.0:
-    resolution: {integrity: sha512-w7yEiaITh2WzKe6u8ZdeLKCUz43wdmY/OqAmsB/PGDvvhTcVhCJ6f0W/RprZL1IhqH8wALoWiwEh/Wer7ZviMQ==}
+  /@volar/vue-language-core@1.6.3:
+    resolution: {integrity: sha512-e9OTDCPa8Wuh0ORhD4z++qTIcrsrqcI9waspr93YcQCq6j+Q+JTFuy7HBSQgyezSAsP6x1WWokKVk4fWWDJQOw==}
     dependencies:
-      '@volar/language-core': 1.3.0-alpha.0
-      '@volar/source-map': 1.3.0-alpha.0
-      '@vue/compiler-dom': 3.2.47
-      '@vue/compiler-sfc': 3.2.47
-      '@vue/reactivity': 3.2.47
-      '@vue/shared': 3.2.47
-      minimatch: 6.2.0
+      '@volar/language-core': 1.4.1
+      '@volar/source-map': 1.4.1
+      '@vue/compiler-dom': 3.3.0-beta.3
+      '@vue/compiler-sfc': 3.3.0-beta.3
+      '@vue/reactivity': 3.3.0-beta.3
+      '@vue/shared': 3.3.0-beta.3
+      minimatch: 9.0.0
       muggle-string: 0.2.2
       vue-template-compiler: 2.7.14
     dev: true
 
-  /@volar/vue-typescript@1.2.0:
-    resolution: {integrity: sha512-zjmRi9y3J1EkG+pfuHp8IbHmibihrKK485cfzsHjiuvJMGrpkWvlO5WVEk8oslMxxeGC5XwBFE9AOlvh378EPA==}
+  /@volar/vue-typescript@1.6.3(typescript@5.0.4):
+    resolution: {integrity: sha512-Dz29Qym33P1MSZDTZJ6PPLN1TLQfkX+g2pRnHqLCsFdSUu4yWYnElBURCn5WJkekxV/v+k2T43aur2RCSY3Ovg==}
+    peerDependencies:
+      typescript: '*'
     dependencies:
-      '@volar/typescript': 1.3.0-alpha.0
-      '@volar/vue-language-core': 1.2.0
+      '@volar/typescript': 1.4.1(typescript@5.0.4)
+      '@volar/vue-language-core': 1.6.3
+      typescript: 5.0.4
     dev: true
 
+  /@vue-macros/common@1.3.0(rollup@3.21.3)(vue@3.2.47):
+    resolution: {integrity: sha512-oRK9vdKryXtJbfucRla8XdnQiWVVNHEBid0waacdfMJn+LOunWeU/3k8VoZZc328HmmZj69MGkUoMWixsHCHGg==}
+    engines: {node: '>=14.19.0'}
+    peerDependencies:
+      vue: ^2.7.0 || ^3.2.25
+    peerDependenciesMeta:
+      vue:
+        optional: true
+    dependencies:
+      '@babel/types': 7.21.4
+      '@rollup/pluginutils': 5.0.2(rollup@3.21.3)
+      '@vue/compiler-sfc': 3.3.0-beta.2
+      local-pkg: 0.4.3
+      magic-string-ast: 0.1.2
+      vue: 3.2.47
+    transitivePeerDependencies:
+      - rollup
+    dev: false
+
+  /@vue-macros/reactivity-transform@0.3.5(rollup@3.21.3)(vue@3.2.47):
+    resolution: {integrity: sha512-HDWPMytAp32uC4aXuLITsBkxGI8yppmthGSSYJENXPvovnIctGV7q6mMNkr9cJMjyr6pjE1rv0y0Vc7SUhx/Xw==}
+    engines: {node: '>=14.19.0'}
+    peerDependencies:
+      vue: ^2.7.0 || ^3.2.25
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue-macros/common': 1.3.0(rollup@3.21.3)(vue@3.2.47)
+      '@vue/compiler-core': 3.3.0-beta.2
+      '@vue/shared': 3.3.0-beta.2
+      magic-string: 0.30.0
+      unplugin: 1.3.1
+      vue: 3.2.47
+    transitivePeerDependencies:
+      - rollup
+    dev: false
+
   /@vue/compiler-core@3.2.47:
     resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==}
     dependencies:
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@vue/shared': 3.2.47
       estree-walker: 2.0.2
       source-map: 0.6.1
 
+  /@vue/compiler-core@3.3.0-beta.2:
+    resolution: {integrity: sha512-Z2VZCL9Rr1gVgyALHIRP+lNFjgfs/K4aTxvJYQ2vhgEAaI0/L6wtG5sr/gOP+MgxwGQV0PvA+iDG3Y3PC7rTEg==}
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue/shared': 3.3.0-beta.2
+      estree-walker: 2.0.2
+      source-map-js: 1.0.2
+    dev: false
+
+  /@vue/compiler-core@3.3.0-beta.3:
+    resolution: {integrity: sha512-mv2rPo4JHou6ebm7+U/wO1HpA6W1zDfTqbt4fqjoXrMwU4DWNgRcLKTXG6G3cXV4mOe+2YgWspfxEzo7fPTMKg==}
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue/shared': 3.3.0-beta.3
+      estree-walker: 2.0.2
+      source-map-js: 1.0.2
+    dev: true
+
   /@vue/compiler-dom@3.2.47:
     resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
     dependencies:
       '@vue/compiler-core': 3.2.47
       '@vue/shared': 3.2.47
 
+  /@vue/compiler-dom@3.3.0-beta.2:
+    resolution: {integrity: sha512-9LPRdCj66OwmUiPa9nuKiaoyKxlFT56j+io8nK/aW5OLl1UkY//Lj661fmDkTY20oLmArt73fAuHD913w4hRqA==}
+    dependencies:
+      '@vue/compiler-core': 3.3.0-beta.2
+      '@vue/shared': 3.3.0-beta.2
+    dev: false
+
+  /@vue/compiler-dom@3.3.0-beta.3:
+    resolution: {integrity: sha512-e7VpjN9wYiuJdJos6Uoe501CzdMkfaEr/27Ks4Ss7Irtcj5YA/S1OROZ35Xl2Pc3ctx6beq5RpcOvnMqh0hcaA==}
+    dependencies:
+      '@vue/compiler-core': 3.3.0-beta.3
+      '@vue/shared': 3.3.0-beta.3
+    dev: true
+
   /@vue/compiler-sfc@2.7.14:
     resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
     dependencies:
-      '@babel/parser': 7.21.3
-      postcss: 8.4.21
+      '@babel/parser': 7.21.4
+      postcss: 8.4.23
       source-map: 0.6.1
     dev: false
 
@@ -7476,26 +7373,96 @@ packages:
       postcss: 8.4.21
       source-map: 0.6.1
 
+  /@vue/compiler-sfc@3.3.0-beta.2:
+    resolution: {integrity: sha512-5FmcQ5LIpM/Y22dTxnxWPD04jC2gr6XSVVqQNY0y776F1P9x4f06fIpMibL58aKU07Th2z4Ab3oPg/Cg1QNVmA==}
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue/compiler-core': 3.3.0-beta.2
+      '@vue/compiler-dom': 3.3.0-beta.2
+      '@vue/compiler-ssr': 3.3.0-beta.2
+      '@vue/reactivity-transform': 3.3.0-beta.2
+      '@vue/shared': 3.3.0-beta.2
+      estree-walker: 2.0.2
+      magic-string: 0.30.0
+      postcss: 8.4.21
+      source-map-js: 1.0.2
+    dev: false
+
+  /@vue/compiler-sfc@3.3.0-beta.3:
+    resolution: {integrity: sha512-6shZNooetShjSMHJvgVoE0EM8pOMV5vnrzsHoCU06stzV+kqRJQpbN7xf2s9wK2fgHMIBSMINrM9AuZiQnNCJg==}
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue/compiler-core': 3.3.0-beta.3
+      '@vue/compiler-dom': 3.3.0-beta.3
+      '@vue/compiler-ssr': 3.3.0-beta.3
+      '@vue/reactivity-transform': 3.3.0-beta.3
+      '@vue/shared': 3.3.0-beta.3
+      estree-walker: 2.0.2
+      magic-string: 0.30.0
+      postcss: 8.4.23
+      source-map-js: 1.0.2
+    dev: true
+
   /@vue/compiler-ssr@3.2.47:
     resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
     dependencies:
       '@vue/compiler-dom': 3.2.47
       '@vue/shared': 3.2.47
 
+  /@vue/compiler-ssr@3.3.0-beta.2:
+    resolution: {integrity: sha512-Xg9od6GvHwfEpnTxMQR+KlKG1nbOHWRLHCiSA0FENiSDTjCDHh0ClzZLhIZUZJD75miyE9ia5ZQF6vpw680rCw==}
+    dependencies:
+      '@vue/compiler-dom': 3.3.0-beta.2
+      '@vue/shared': 3.3.0-beta.2
+    dev: false
+
+  /@vue/compiler-ssr@3.3.0-beta.3:
+    resolution: {integrity: sha512-egJ0lEVAod3Hpnw96cJ/0a9qv5f5h5/VCBpKYT8scqkzoMsikh8AJant2omokBCL/Ut5UAMLVQlA5b66+2Ys/g==}
+    dependencies:
+      '@vue/compiler-dom': 3.3.0-beta.3
+      '@vue/shared': 3.3.0-beta.3
+    dev: true
+
   /@vue/reactivity-transform@3.2.47:
     resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==}
     dependencies:
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@vue/compiler-core': 3.2.47
       '@vue/shared': 3.2.47
       estree-walker: 2.0.2
       magic-string: 0.25.9
 
+  /@vue/reactivity-transform@3.3.0-beta.2:
+    resolution: {integrity: sha512-EUL53/rsd+hrqhCa/SrhXQ6PzMZJfLQt39xQlzr0Sxsdv/bg5lqbcK9YtGkjYohRuSp1QneFU78LEsQ9j4B2Dw==}
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue/compiler-core': 3.3.0-beta.2
+      '@vue/shared': 3.3.0-beta.2
+      estree-walker: 2.0.2
+      magic-string: 0.30.0
+    dev: false
+
+  /@vue/reactivity-transform@3.3.0-beta.3:
+    resolution: {integrity: sha512-aM3TgBca9QMMu/9B9ASRVvckeZpAdJO9nmQh5UCznhoDYjVxQPS+sCQvH6TLOjPB1MDQMVQYg4ZiPqfVVo7NbA==}
+    dependencies:
+      '@babel/parser': 7.21.4
+      '@vue/compiler-core': 3.3.0-beta.3
+      '@vue/shared': 3.3.0-beta.3
+      estree-walker: 2.0.2
+      magic-string: 0.30.0
+    dev: true
+
   /@vue/reactivity@3.2.47:
     resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==}
     dependencies:
       '@vue/shared': 3.2.47
 
+  /@vue/reactivity@3.3.0-beta.3:
+    resolution: {integrity: sha512-9VjWfWgZJ18YXEkfnDfZr33RyLBa6zc0RARLkMqMApWvM26eusZAZ4hhyxlgODBU/mEFk4XOGIAtwwSQedA0MQ==}
+    dependencies:
+      '@vue/shared': 3.3.0-beta.3
+    dev: true
+
   /@vue/runtime-core@3.2.47:
     resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==}
     dependencies:
@@ -7521,6 +7488,14 @@ packages:
   /@vue/shared@3.2.47:
     resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
 
+  /@vue/shared@3.3.0-beta.2:
+    resolution: {integrity: sha512-AsHYKYiYUnL/LHog6iV/G9tctFZYOsaxHDbSnfeyip94rjndO46XSDbHek7wDlcj3NHGaf8jAQQKfva/7mypjA==}
+    dev: false
+
+  /@vue/shared@3.3.0-beta.3:
+    resolution: {integrity: sha512-st1SnB/Bkbb9TsieeI4TRX9TqHYIR5wvIma3ZtEben55EYSWa1q5u2BhTNgABSdH+rv3Xwfrvpwh5PmCw6Y53g==}
+    dev: true
+
   /@vue/test-utils@2.3.2(vue@3.2.47):
     resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==}
     peerDependencies:
@@ -7533,8 +7508,8 @@ packages:
       '@vue/server-renderer': 3.2.47(vue@3.2.47)
     dev: true
 
-  /@webgpu/types@0.1.21:
-    resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==}
+  /@webgpu/types@0.1.30:
+    resolution: {integrity: sha512-9AXJSmL3MzY8ZL//JjudA//q+2kBRGhLBFpkdGksWIuxrMy81nFrCzj2Am+mbh8WoU6rXmv7cY5E3rdlyru2Qg==}
     dev: false
 
   /@xmldom/xmldom@0.8.6:
@@ -7571,21 +7546,21 @@ packages:
       p-limit: 2.3.0
       pluralize: 7.0.0
       pretty-bytes: 5.6.0
-      semver: 7.3.8
+      semver: 7.5.0
       stream-to-promise: 2.2.0
       tar-stream: 2.2.0
       treeify: 1.1.0
       tslib: 1.14.1
       tunnel: 0.0.6
-    dev: true
+    dev: false
 
-  /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.15):
+  /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.17.18):
     resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
     engines: {node: '>=14.15.0'}
     peerDependencies:
       esbuild: '>=0.10.0'
     dependencies:
-      esbuild: 0.17.15
+      esbuild: 0.17.18
       tslib: 2.5.0
     dev: true
 
@@ -7595,7 +7570,7 @@ packages:
     dependencies:
       '@yarnpkg/libzip': 2.3.0
       tslib: 1.14.1
-    dev: true
+    dev: false
 
   /@yarnpkg/json-proxy@2.1.1:
     resolution: {integrity: sha512-meUiCAgCYpXTH1qJfqfz+dX013ohW9p2dKfwIzUYAFutH+lsz1eHPBIk72cuCV84adh9gX6j66ekBKH/bIhCQw==}
@@ -7603,7 +7578,7 @@ packages:
     dependencies:
       '@yarnpkg/fslib': 2.10.2
       tslib: 1.14.1
-    dev: true
+    dev: false
 
   /@yarnpkg/libzip@2.3.0:
     resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==}
@@ -7611,11 +7586,11 @@ packages:
     dependencies:
       '@types/emscripten': 1.39.6
       tslib: 1.14.1
-    dev: true
+    dev: false
 
   /@yarnpkg/lockfile@1.1.0:
     resolution: {integrity: sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==}
-    dev: true
+    dev: false
 
   /@yarnpkg/parsers@2.5.1:
     resolution: {integrity: sha512-KtYN6Ez3x753vPF9rETxNTPnPjeaHY11Exlpqb4eTII7WRlnGiZ5rvvQBau4R20Ik5KBv+vS3EJEcHyCunwzzw==}
@@ -7623,7 +7598,7 @@ packages:
     dependencies:
       js-yaml: 3.14.1
       tslib: 1.14.1
-    dev: true
+    dev: false
 
   /@yarnpkg/pnp@2.3.2:
     resolution: {integrity: sha512-JdwHu1WBCISqJEhIwx6Hbpe8MYsYbkGMxoxolkDiAeJ9IGEe08mQcbX1YmUDV1ozSWlm9JZE90nMylcDsXRFpA==}
@@ -7632,7 +7607,7 @@ packages:
       '@types/node': 13.13.52
       '@yarnpkg/fslib': 2.10.2
       tslib: 1.14.1
-    dev: true
+    dev: false
 
   /@yarnpkg/shell@2.4.1:
     resolution: {integrity: sha512-oNNJkH8ZI5uwu0dMkJf737yMSY1WXn9gp55DqSA5wAOhKvV5DJTXFETxkVgBQhO6Bow9tMGSpvowTMD/oAW/9g==}
@@ -7647,7 +7622,7 @@ packages:
       micromatch: 4.0.5
       stream-buffers: 3.0.2
       tslib: 1.14.1
-    dev: true
+    dev: false
 
   /@zxing/text-encoding@0.9.0:
     resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
@@ -7850,11 +7825,6 @@ packages:
     resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
     engines: {node: '>=8'}
 
-  /ansi-regex@6.0.1:
-    resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
-    engines: {node: '>=12'}
-    dev: true
-
   /ansi-styles@2.2.1:
     resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
     engines: {node: '>=0.10.0'}
@@ -7878,11 +7848,6 @@ packages:
     engines: {node: '>=10'}
     dev: true
 
-  /ansi-styles@6.2.1:
-    resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
-    engines: {node: '>=12'}
-    dev: true
-
   /ansi-wrap@0.1.0:
     resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==}
     engines: {node: '>=0.10.0'}
@@ -7890,6 +7855,7 @@ packages:
 
   /any-promise@1.3.0:
     resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+    dev: false
 
   /anymatch@2.0.0:
     resolution: {integrity: sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==}
@@ -8480,7 +8446,7 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       bin-version: 6.0.0
-      semver: 7.3.8
+      semver: 7.5.0
       semver-truncate: 2.0.0
     dev: false
 
@@ -8511,7 +8477,7 @@ packages:
   /binjumper@0.1.4:
     resolution: {integrity: sha512-Gdxhj+U295tIM6cO4bJO1jsvSjBVHNpj2o/OwW7pqDEtaqF6KdOxjtbo93jMMKAkP7+u09+bV8DhSqjIv4qR3w==}
     engines: {node: '>=10.12.0'}
-    dev: true
+    dev: false
 
   /bl@4.1.0:
     resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -8532,6 +8498,10 @@ packages:
     resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==}
     dev: true
 
+  /blueimp-md5@2.19.0:
+    resolution: {integrity: sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==}
+    dev: true
+
   /blurhash@2.0.5:
     resolution: {integrity: sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==}
     dev: false
@@ -8733,10 +8703,10 @@ packages:
       cron-parser: 4.7.1
       debuglog: 1.0.1
       get-port: 5.1.1
-      ioredis: 5.2.4
+      ioredis: 5.3.2
       lodash: 4.17.21
       msgpackr: 1.8.1
-      semver: 7.3.8
+      semver: 7.5.0
       uuid: 8.3.2
     transitivePeerDependencies:
       - supports-color
@@ -8825,6 +8795,7 @@ packages:
   /cacheable-lookup@5.0.4:
     resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
     engines: {node: '>=10.6.0'}
+    dev: false
 
   /cacheable-lookup@6.1.0:
     resolution: {integrity: sha512-KJ/Dmo1lDDhmW2XDPMo+9oiy/CeqosPguPCrgcVzKyZrL6pM1gU2GmPY/xo6OQPTUaA/c0kwHuywB4E6nmT9ww==}
@@ -8858,6 +8829,7 @@ packages:
       lowercase-keys: 2.0.0
       normalize-url: 6.1.0
       responselike: 2.0.1
+    dev: false
 
   /cachedir@2.3.0:
     resolution: {integrity: sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==}
@@ -8984,7 +8956,6 @@ packages:
     dependencies:
       ansi-styles: 4.3.0
       supports-color: 7.2.0
-    dev: true
 
   /chalk@4.1.1:
     resolution: {integrity: sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==}
@@ -9031,45 +9002,45 @@ packages:
     resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
     dev: true
 
-  /chart.js@4.2.1:
-    resolution: {integrity: sha512-6YbpQ0nt3NovAgOzbkSSeeAQu/3za1319dPUQTXn9WcOpywM8rGKxJHrhS8V8xEkAlk8YhEfjbuAPfUyp6jIsw==}
-    engines: {pnpm: ^7.0.0}
+  /chart.js@4.3.0:
+    resolution: {integrity: sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==}
+    engines: {pnpm: '>=7'}
     dependencies:
       '@kurkle/color': 0.3.2
     dev: false
 
-  /chartjs-adapter-date-fns@3.0.0(chart.js@4.2.1)(date-fns@2.29.3):
+  /chartjs-adapter-date-fns@3.0.0(chart.js@4.3.0)(date-fns@2.30.0):
     resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==}
     peerDependencies:
       chart.js: '>=2.8.0'
       date-fns: '>=2.0.0'
     dependencies:
-      chart.js: 4.2.1
-      date-fns: 2.29.3
+      chart.js: 4.3.0
+      date-fns: 2.30.0
     dev: false
 
-  /chartjs-chart-matrix@2.0.1(chart.js@4.2.1):
+  /chartjs-chart-matrix@2.0.1(chart.js@4.3.0):
     resolution: {integrity: sha512-BGfeY+/PHnITyDlc7WfnKJ1RyOfgOzIqWp/gxzzl7pUjyoGzHDcw51qd2xJF9gdT9Def7ZwOnOMm8GJUXDxI0w==}
     peerDependencies:
       chart.js: '>=3.0.0'
     dependencies:
-      chart.js: 4.2.1
+      chart.js: 4.3.0
     dev: false
 
-  /chartjs-plugin-gradient@0.6.1(chart.js@4.2.1):
+  /chartjs-plugin-gradient@0.6.1(chart.js@4.3.0):
     resolution: {integrity: sha512-TGHNIh8KqQMLdb+UfY80cBHYRyOC47eeokmgkeajRdKGbFt462lJiyiq4ZJ25fiM7BGsmzoBLhmVyEw4B3gQxw==}
     peerDependencies:
       chart.js: '>=2.6.0'
     dependencies:
-      chart.js: 4.2.1
+      chart.js: 4.3.0
     dev: false
 
-  /chartjs-plugin-zoom@2.0.1(chart.js@4.2.1):
+  /chartjs-plugin-zoom@2.0.1(chart.js@4.3.0):
     resolution: {integrity: sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==}
     peerDependencies:
       chart.js: '>=3.2.0'
     dependencies:
-      chart.js: 4.2.1
+      chart.js: 4.3.0
       hammerjs: 2.0.8
     dev: false
 
@@ -9145,11 +9116,11 @@ packages:
       snyk-nodejs-lockfile-parser: 1.48.2
     transitivePeerDependencies:
       - supports-color
-    dev: true
+    dev: false
 
   /ci-info@2.0.0:
     resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
-    dev: true
+    dev: false
 
   /ci-info@3.7.1:
     resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==}
@@ -9226,14 +9197,6 @@ packages:
       string-width: 4.2.3
     dev: true
 
-  /cli-truncate@3.1.0:
-    resolution: {integrity: sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==}
-    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-    dependencies:
-      slice-ansi: 5.0.0
-      string-width: 5.1.2
-    dev: true
-
   /cli-width@3.0.0:
     resolution: {integrity: sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==}
     engines: {node: '>= 10'}
@@ -9241,7 +9204,7 @@ packages:
 
   /clipanion@2.6.2:
     resolution: {integrity: sha512-0tOHJNMF9+4R3qcbBL+4IxLErpaYSYvzs10aXuECDbZdJOuJHdagJMAqvLdeaUQTI/o2uSCDRpet6ywDiKOAYw==}
-    dev: true
+    dev: false
 
   /cliui@3.2.0:
     resolution: {integrity: sha512-0yayqDxWQbqk3ojkYqUKqaAQ6AfNKeKWRNA8kR0WXzAsdHpP4BIaOmMAG87JGuO6qcobyW4GjxHd9PmhEd+T9w==}
@@ -9300,6 +9263,7 @@ packages:
     resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==}
     dependencies:
       mimic-response: 1.0.1
+    dev: false
 
   /clone-stats@1.0.0:
     resolution: {integrity: sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==}
@@ -9396,7 +9360,6 @@ packages:
     dependencies:
       color-name: 1.1.4
       simple-swizzle: 0.2.2
-    dev: false
 
   /color-support@1.1.3:
     resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
@@ -9416,7 +9379,6 @@ packages:
     dependencies:
       color-convert: 2.0.1
       color-string: 1.9.1
-    dev: false
 
   /colorette@2.0.19:
     resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
@@ -9453,11 +9415,6 @@ packages:
   /commander@2.20.3:
     resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
 
-  /commander@5.1.0:
-    resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
-    engines: {node: '>= 6'}
-    dev: true
-
   /commander@6.2.1:
     resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==}
     engines: {node: '>= 6'}
@@ -9532,6 +9489,20 @@ packages:
       readable-stream: 2.3.7
       typedarray: 0.0.6
 
+  /concordance@5.0.4:
+    resolution: {integrity: sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==}
+    engines: {node: '>=10.18.0 <11 || >=12.14.0 <13 || >=14'}
+    dependencies:
+      date-time: 3.1.0
+      esutils: 2.0.3
+      fast-diff: 1.2.0
+      js-string-escape: 1.0.1
+      lodash: 4.17.21
+      md5-hex: 3.0.1
+      semver: 7.5.0
+      well-known-symbols: 2.0.0
+    dev: true
+
   /config-chain@1.1.13:
     resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==}
     dependencies:
@@ -9549,7 +9520,7 @@ packages:
   /constantinople@4.0.1:
     resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
     dependencies:
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/types': 7.21.4
 
   /content-disposition@0.5.4:
@@ -9600,11 +9571,6 @@ packages:
       browserslist: 4.21.5
     dev: true
 
-  /core-js@3.27.1:
-    resolution: {integrity: sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==}
-    requiresBuild: true
-    dev: false
-
   /core-js@3.29.1:
     resolution: {integrity: sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==}
     requiresBuild: true
@@ -9655,7 +9621,6 @@ packages:
       node-fetch: 2.6.7
     transitivePeerDependencies:
       - encoding
-    dev: true
 
   /cross-spawn@5.1.0:
     resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
@@ -9772,8 +9737,8 @@ packages:
       uniq: 1.0.1
     dev: false
 
-  /cypress@12.9.0:
-    resolution: {integrity: sha512-Ofe09LbHKgSqX89Iy1xen2WvpgbvNxDzsWx3mgU1mfILouELeXYGwIib3ItCwoRrRifoQwcBFmY54Vs0zw7QCg==}
+  /cypress@12.11.0:
+    resolution: {integrity: sha512-TJE+CCWI26Hwr5Msb9GpQhFLubdYooW0fmlPwTsfiyxmngqc7+SZGLPeIkj2dTSSZSEtpQVzOzvcnzH0o8G7Vw==}
     engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
     hasBin: true
     requiresBuild: true
@@ -9792,7 +9757,7 @@ packages:
       check-more-types: 2.24.0
       cli-cursor: 3.1.0
       cli-table3: 0.6.3
-      commander: 5.1.0
+      commander: 6.2.1
       common-tags: 1.8.2
       dayjs: 1.11.7
       debug: 4.3.4(supports-color@8.1.1)
@@ -9810,12 +9775,12 @@ packages:
       listr2: 3.14.0(enquirer@2.3.6)
       lodash: 4.17.21
       log-symbols: 4.1.0
-      minimist: 1.2.7
+      minimist: 1.2.8
       ospath: 1.2.2
       pretty-bytes: 5.6.0
       proxy-from-env: 1.0.0
       request-progress: 3.0.0
-      semver: 7.3.8
+      semver: 7.5.0
       supports-color: 8.1.1
       tmp: 0.2.1
       untildify: 4.0.0
@@ -9852,11 +9817,20 @@ packages:
       whatwg-url: 12.0.1
     dev: false
 
-  /date-fns@2.29.3:
-    resolution: {integrity: sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==}
+  /date-fns@2.30.0:
+    resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
     engines: {node: '>=0.11'}
+    dependencies:
+      '@babel/runtime': 7.21.0
     dev: false
 
+  /date-time@3.1.0:
+    resolution: {integrity: sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==}
+    engines: {node: '>=6'}
+    dependencies:
+      time-zone: 1.0.0
+    dev: true
+
   /dayjs@1.11.7:
     resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
     dev: true
@@ -9991,7 +9965,6 @@ packages:
   /deep-extend@0.6.0:
     resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
     engines: {node: '>=4.0.0'}
-    dev: false
 
   /deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -10090,11 +10063,6 @@ packages:
   /delegates@1.0.0:
     resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
 
-  /denque@1.5.1:
-    resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==}
-    engines: {node: '>=0.10'}
-    dev: false
-
   /denque@2.1.0:
     resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
     engines: {node: '>=0.10'}
@@ -10131,7 +10099,6 @@ packages:
   /detect-libc@2.0.1:
     resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==}
     engines: {node: '>=8'}
-    dev: false
 
   /detect-newline@3.1.0:
     resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
@@ -10168,7 +10135,7 @@ packages:
   /diff@4.0.2:
     resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
     engines: {node: '>=0.3.1'}
-    dev: true
+    dev: false
 
   /diff@5.1.0:
     resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==}
@@ -10274,10 +10241,6 @@ packages:
       object.defaults: 1.1.0
     dev: false
 
-  /eastasianwidth@0.2.0:
-    resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
-    dev: true
-
   /ecc-jsbn@0.1.2:
     resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==}
     dependencies:
@@ -10326,10 +10289,6 @@ packages:
   /emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
 
-  /emoji-regex@9.2.2:
-    resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
-    dev: true
-
   /encode-utf8@1.0.3:
     resolution: {integrity: sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==}
     dev: false
@@ -10351,7 +10310,7 @@ packages:
     resolution: {integrity: sha512-EoulkdKF/1xa92q25PbjuDcgJ9RDHYU2Rs3SCIvs2/dSQ3BpmxneNHmA/M7fe60M3PrV7nNGTTNbkK62l6vXiQ==}
     dependencies:
       once: 1.3.3
-    dev: true
+    dev: false
 
   /end-of-stream@1.4.4:
     resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -10510,74 +10469,45 @@ packages:
     resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==}
     dev: true
 
-  /esbuild-register@3.4.2(esbuild@0.17.15):
+  /esbuild-register@3.4.2(esbuild@0.17.18):
     resolution: {integrity: sha512-kG/XyTDyz6+YDuyfB9ZoSIOOmgyFCH+xPRtsCa8W85HLRV5Csp+o3jWVbOSHgSLfyLc5DmP+KFDNwty4mEjC+Q==}
     peerDependencies:
       esbuild: '>=0.12 <1'
     dependencies:
       debug: 4.3.4(supports-color@8.1.1)
-      esbuild: 0.17.15
+      esbuild: 0.17.18
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /esbuild@0.17.14:
-    resolution: {integrity: sha512-vOO5XhmVj/1XQR9NQ1UPq6qvMYL7QFJU57J5fKBKBKxp17uDt5PgxFDb4A2nEiXhr1qQs4x0F5+66hVVw4ruNw==}
-    engines: {node: '>=12'}
-    hasBin: true
-    requiresBuild: true
-    optionalDependencies:
-      '@esbuild/android-arm': 0.17.14
-      '@esbuild/android-arm64': 0.17.14
-      '@esbuild/android-x64': 0.17.14
-      '@esbuild/darwin-arm64': 0.17.14
-      '@esbuild/darwin-x64': 0.17.14
-      '@esbuild/freebsd-arm64': 0.17.14
-      '@esbuild/freebsd-x64': 0.17.14
-      '@esbuild/linux-arm': 0.17.14
-      '@esbuild/linux-arm64': 0.17.14
-      '@esbuild/linux-ia32': 0.17.14
-      '@esbuild/linux-loong64': 0.17.14
-      '@esbuild/linux-mips64el': 0.17.14
-      '@esbuild/linux-ppc64': 0.17.14
-      '@esbuild/linux-riscv64': 0.17.14
-      '@esbuild/linux-s390x': 0.17.14
-      '@esbuild/linux-x64': 0.17.14
-      '@esbuild/netbsd-x64': 0.17.14
-      '@esbuild/openbsd-x64': 0.17.14
-      '@esbuild/sunos-x64': 0.17.14
-      '@esbuild/win32-arm64': 0.17.14
-      '@esbuild/win32-ia32': 0.17.14
-      '@esbuild/win32-x64': 0.17.14
-
-  /esbuild@0.17.15:
-    resolution: {integrity: sha512-LBUV2VsUIc/iD9ME75qhT4aJj0r75abCVS0jakhFzOtR7TQsqQA5w0tZ+KTKnwl3kXE0MhskNdHDh/I5aCR1Zw==}
+  /esbuild@0.17.18:
+    resolution: {integrity: sha512-z1lix43jBs6UKjcZVKOw2xx69ffE2aG0PygLL5qJ9OS/gy0Ewd1gW/PUQIOIQGXBHWNywSc0floSKoMFF8aK2w==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
     optionalDependencies:
-      '@esbuild/android-arm': 0.17.15
-      '@esbuild/android-arm64': 0.17.15
-      '@esbuild/android-x64': 0.17.15
-      '@esbuild/darwin-arm64': 0.17.15
-      '@esbuild/darwin-x64': 0.17.15
-      '@esbuild/freebsd-arm64': 0.17.15
-      '@esbuild/freebsd-x64': 0.17.15
-      '@esbuild/linux-arm': 0.17.15
-      '@esbuild/linux-arm64': 0.17.15
-      '@esbuild/linux-ia32': 0.17.15
-      '@esbuild/linux-loong64': 0.17.15
-      '@esbuild/linux-mips64el': 0.17.15
-      '@esbuild/linux-ppc64': 0.17.15
-      '@esbuild/linux-riscv64': 0.17.15
-      '@esbuild/linux-s390x': 0.17.15
-      '@esbuild/linux-x64': 0.17.15
-      '@esbuild/netbsd-x64': 0.17.15
-      '@esbuild/openbsd-x64': 0.17.15
-      '@esbuild/sunos-x64': 0.17.15
-      '@esbuild/win32-arm64': 0.17.15
-      '@esbuild/win32-ia32': 0.17.15
-      '@esbuild/win32-x64': 0.17.15
+      '@esbuild/android-arm': 0.17.18
+      '@esbuild/android-arm64': 0.17.18
+      '@esbuild/android-x64': 0.17.18
+      '@esbuild/darwin-arm64': 0.17.18
+      '@esbuild/darwin-x64': 0.17.18
+      '@esbuild/freebsd-arm64': 0.17.18
+      '@esbuild/freebsd-x64': 0.17.18
+      '@esbuild/linux-arm': 0.17.18
+      '@esbuild/linux-arm64': 0.17.18
+      '@esbuild/linux-ia32': 0.17.18
+      '@esbuild/linux-loong64': 0.17.18
+      '@esbuild/linux-mips64el': 0.17.18
+      '@esbuild/linux-ppc64': 0.17.18
+      '@esbuild/linux-riscv64': 0.17.18
+      '@esbuild/linux-s390x': 0.17.18
+      '@esbuild/linux-x64': 0.17.18
+      '@esbuild/netbsd-x64': 0.17.18
+      '@esbuild/openbsd-x64': 0.17.18
+      '@esbuild/sunos-x64': 0.17.18
+      '@esbuild/win32-arm64': 0.17.18
+      '@esbuild/win32-ia32': 0.17.18
+      '@esbuild/win32-x64': 0.17.18
 
   /escalade@3.1.1:
     resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
@@ -10643,7 +10573,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint@8.37.0):
+  /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.59.2)(eslint-import-resolver-node@0.3.7)(eslint@8.39.0):
     resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -10664,15 +10594,15 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+      '@typescript-eslint/parser': 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       debug: 3.2.7(supports-color@8.1.1)
-      eslint: 8.37.0
+      eslint: 8.39.0
       eslint-import-resolver-node: 0.3.7
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.57.1)(eslint@8.37.0):
+  /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.2)(eslint@8.39.0):
     resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -10682,15 +10612,15 @@ packages:
       '@typescript-eslint/parser':
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 5.57.1(eslint@8.37.0)(typescript@5.0.3)
+      '@typescript-eslint/parser': 5.59.2(eslint@8.39.0)(typescript@5.0.4)
       array-includes: 3.1.6
       array.prototype.flat: 1.3.1
       array.prototype.flatmap: 1.3.1
       debug: 3.2.7(supports-color@8.1.1)
       doctrine: 2.1.0
-      eslint: 8.37.0
+      eslint: 8.39.0
       eslint-import-resolver-node: 0.3.7
-      eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.57.1)(eslint-import-resolver-node@0.3.7)(eslint@8.37.0)
+      eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.2)(eslint-import-resolver-node@0.3.7)(eslint@8.39.0)
       has: 1.0.3
       is-core-module: 2.11.0
       is-glob: 4.0.3
@@ -10705,19 +10635,19 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-vue@9.10.0(eslint@8.37.0):
-    resolution: {integrity: sha512-2MgP31OBf8YilUvtakdVMc8xVbcMp7z7/iQj8LHVpXrSXHPXSJRUIGSPFI6b6pyCx/buKaFJ45ycqfHvQRiW2g==}
+  /eslint-plugin-vue@9.11.0(eslint@8.39.0):
+    resolution: {integrity: sha512-bBCJAZnkBV7ATH4Z1E7CvN3nmtS4H7QUU3UBxPdo8WohRU+yHjnQRALpTbxMVcz0e4Mx3IyxIdP5HYODMxK9cQ==}
     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.37.0)
-      eslint: 8.37.0
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.39.0)
+      eslint: 8.39.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
       postcss-selector-parser: 6.0.11
-      semver: 7.3.8
-      vue-eslint-parser: 9.1.1(eslint@8.37.0)
+      semver: 7.5.0
+      vue-eslint-parser: 9.1.1(eslint@8.39.0)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -10743,20 +10673,28 @@ packages:
       estraverse: 5.3.0
     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-visitor-keys@3.4.0:
     resolution: {integrity: sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /eslint@8.37.0:
-    resolution: {integrity: sha512-NU3Ps9nI05GUoVMxcZx1J8CNR6xOvUT4jAUMH5+z8lpp3aEdPVCImKw6PWG4PY+Vfkpr+jvMpxs/qoE7wq0sPw==}
+  /eslint@8.39.0:
+    resolution: {integrity: sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.37.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.39.0)
       '@eslint-community/regexpp': 4.5.0
       '@eslint/eslintrc': 2.0.2
-      '@eslint/js': 8.37.0
+      '@eslint/js': 8.39.0
       '@humanwhocodes/config-array': 0.11.8
       '@humanwhocodes/module-importer': 1.0.1
       '@nodelib/fs.walk': 1.2.8
@@ -10766,7 +10704,7 @@ packages:
       debug: 4.3.4(supports-color@8.1.1)
       doctrine: 3.0.0
       escape-string-regexp: 4.0.0
-      eslint-scope: 7.1.1
+      eslint-scope: 7.2.0
       eslint-visitor-keys: 3.4.0
       espree: 9.5.1
       esquery: 1.4.2
@@ -10845,7 +10783,7 @@ packages:
     engines: {node: '>=8.3.0'}
     dependencies:
       '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.3
+      '@babel/types': 7.21.4
       c8: 7.13.0
     transitivePeerDependencies:
       - supports-color
@@ -10867,7 +10805,7 @@ packages:
     resolution: {integrity: sha512-KB44sV4Mv7uLIkJHJ5qhiZe5um6th2g57nHQL/uqnPHKP2IswoTRWUteEXTJQL4gW++1zqWUni+H2hGkP51c9w==}
     dependencies:
       tslib: 2.5.0
-    dev: true
+    dev: false
 
   /event-stream@3.3.4:
     resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==}
@@ -10894,8 +10832,8 @@ packages:
     resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
     dev: false
 
-  /eventemitter3@5.0.0:
-    resolution: {integrity: sha512-riuVbElZZNXLeLEoprfNYoDSwTBRR44X3mnhdI1YcnENpWTCsTTVZ2zFuqQcpoyqPQIUXdiPEU0ECAq0KQRaHg==}
+  /eventemitter3@5.0.1:
+    resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
     dev: false
 
   /events@3.3.0:
@@ -10987,7 +10925,6 @@ packages:
   /expand-template@2.0.3:
     resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
     engines: {node: '>=6'}
-    dev: false
 
   /expand-tilde@2.0.2:
     resolution: {integrity: sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==}
@@ -11158,6 +11095,10 @@ packages:
   /fast-deep-equal@3.1.3:
     resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
 
+  /fast-diff@1.2.0:
+    resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==}
+    dev: true
+
   /fast-glob@3.2.12:
     resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==}
     engines: {node: '>=8.6.0'}
@@ -11171,8 +11112,8 @@ packages:
   /fast-json-stable-stringify@2.1.0:
     resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
 
-  /fast-json-stringify@5.5.0:
-    resolution: {integrity: sha512-rmw2Z8/mLkND8zI+3KTYIkNPEoF5v6GqDP/o+g7H3vjdWjBwuKpgAYFHIzL6ORRB+iqDjjtJnLIW9Mzxn5szOA==}
+  /fast-json-stringify@5.7.0:
+    resolution: {integrity: sha512-sBVPTgnAZseLu1Qgj6lUbQ0HfjFhZWXAmpZ5AaSGkyLh5gAXBga/uPJjQPHpDFjC9adWIpdOcCLSDTgrZ7snoQ==}
     dependencies:
       '@fastify/deepmerge': 1.3.0
       ajv: 8.12.0
@@ -11225,15 +11166,16 @@ packages:
     resolution: {integrity: sha512-79ak0JxddO0utAXAQ5ccKhvs6vX2MGyHHMMsmZkBANrq3hXc1CHzvNPHOcvTsVMEPl5I+NT+RO4YKMGehOfSIg==}
     dev: false
 
-  /fastify@4.15.0:
-    resolution: {integrity: sha512-m/CaRN8nf5uyYdrDe2qqq+0z3oGyE+A++qlKQoLJTI4WI0nWK9D6R3FxXQ3MVwt/md977GMR4F43pE9oqrS2zw==}
+  /fastify@4.17.0:
+    resolution: {integrity: sha512-tzuY1tgWJo2Y6qEKwmLhFvACUmr68Io2pqP/sDKU71KRM6A6R3DrCDqLGqANbeLZcKUfdfY58ut35CGqemcTgg==}
     dependencies:
       '@fastify/ajv-compiler': 3.5.0
       '@fastify/error': 3.2.0
-      '@fastify/fast-json-stringify-compiler': 4.2.0
+      '@fastify/fast-json-stringify-compiler': 4.3.0
       abstract-logging: 2.0.1
       avvio: 8.2.0
       fast-content-type-parse: 1.0.0
+      fast-json-stringify: 5.7.0
       find-my-way: 7.6.0
       light-my-request: 5.8.0
       pino: 8.8.0
@@ -11241,8 +11183,8 @@ packages:
       proxy-addr: 2.0.7
       rfdc: 1.3.0
       secure-json-parse: 2.7.0
-      semver: 7.3.8
-      tiny-lru: 10.0.1
+      semver: 7.5.0
+      tiny-lru: 11.0.1
     transitivePeerDependencies:
       - supports-color
     dev: false
@@ -11318,8 +11260,8 @@ packages:
       token-types: 5.0.1
     dev: false
 
-  /file-type@18.2.1:
-    resolution: {integrity: sha512-Yw5MtnMv7vgD2/6Bjmmuegc8bQEVA9GmAyaR18bMYWKqsWDG9wgYZ1j4I6gNMF5Y5JBDcUcjRQqNQx7Y8uotcg==}
+  /file-type@18.3.0:
+    resolution: {integrity: sha512-pkPZ5OGIq0TYb37b8bHDLNeQSe1H2KlaQ2ySGpJkkr2KZdaWsO4QhPzHA0mQcsUW2cSqJk+4gM/UyLz/UFbXdQ==}
     engines: {node: '>=14.16'}
     dependencies:
       readable-web-to-node-stream: 3.0.2
@@ -11860,7 +11802,6 @@ packages:
 
   /github-from-package@0.0.0:
     resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
-    dev: false
 
   /github-slugger@1.5.0:
     resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
@@ -12044,6 +11985,7 @@ packages:
       lowercase-keys: 2.0.0
       p-cancelable: 2.1.1
       responselike: 2.0.1
+    dev: false
 
   /got@12.6.0:
     resolution: {integrity: sha512-WTcaQ963xV97MN3x0/CbAriXFZcXCfgxVp91I+Ze6pawQOa7SgzwSx2zIJJsX+kTajMnVs0xcFD1TxZKFqhdnQ==}
@@ -12070,7 +12012,6 @@ packages:
 
   /grapheme-splitter@1.0.4:
     resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
-    dev: true
 
   /graphql@16.6.0:
     resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
@@ -12184,7 +12125,7 @@ packages:
     engines: {node: '>=0.4.7'}
     hasBin: true
     dependencies:
-      minimist: 1.2.7
+      minimist: 1.2.8
       neo-async: 2.6.2
       source-map: 0.6.1
       wordwrap: 1.0.0
@@ -12192,18 +12133,15 @@ packages:
       uglify-js: 3.17.4
     dev: true
 
-  /happy-dom@8.9.0:
-    resolution: {integrity: sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==}
+  /happy-dom@9.10.2:
+    resolution: {integrity: sha512-PZ+UzoU7S8/has8Zw7tr8WGPAAr1QVrAc9o2Llhtuf6aewnR4DDmhPU1Z8KzWwSjXOE+d+b38qNfwHU35PSMIw==}
     dependencies:
       css.escape: 1.5.1
       he: 1.2.0
       iconv-lite: 0.6.3
-      node-fetch: 2.6.7
       webidl-conversions: 7.0.0
       whatwg-encoding: 2.0.0
       whatwg-mimetype: 3.0.0
-    transitivePeerDependencies:
-      - encoding
 
   /har-schema@2.0.0:
     resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==}
@@ -12438,6 +12376,7 @@ packages:
     dependencies:
       quick-lru: 5.1.1
       resolve-alpn: 1.2.1
+    dev: false
 
   /http2-wrapper@2.2.0:
     resolution: {integrity: sha512-kZB0wxMo0sh1PehyjJUWRFEd99KC5TLjZ2cULC4f9iqJBAmKQQXEICjxl5iPJRwP40dpeHFqqhm7tYCvODpqpQ==}
@@ -12600,7 +12539,7 @@ packages:
       mute-stream: 0.0.8
       ora: 5.4.1
       run-async: 2.4.1
-      rxjs: 7.8.0
+      rxjs: 7.8.1
       string-width: 4.2.3
       strip-ansi: 6.0.1
       through: 2.3.8
@@ -12633,27 +12572,8 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: false
 
-  /ioredis@4.28.5:
-    resolution: {integrity: sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==}
-    engines: {node: '>=6'}
-    dependencies:
-      cluster-key-slot: 1.1.2
-      debug: 4.3.4(supports-color@8.1.1)
-      denque: 1.5.1
-      lodash.defaults: 4.2.0
-      lodash.flatten: 4.4.0
-      lodash.isarguments: 3.1.0
-      p-map: 2.1.0
-      redis-commands: 1.7.0
-      redis-errors: 1.2.0
-      redis-parser: 3.0.0
-      standard-as-callback: 2.1.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
-  /ioredis@5.2.4:
-    resolution: {integrity: sha512-qIpuAEt32lZJQ0XyrloCRdlEdUUNGG9i0UOk6zgzK6igyudNWqEBxfH6OlbnOOoBBvr1WB02mm8fR55CnikRng==}
+  /ioredis@5.3.2:
+    resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==}
     engines: {node: '>=12.22.0'}
     dependencies:
       '@ioredis/commands': 1.2.0
@@ -12775,7 +12695,6 @@ packages:
 
   /is-arrayish@0.3.2:
     resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
-    dev: false
 
   /is-bigint@1.0.4:
     resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
@@ -12803,7 +12722,6 @@ packages:
   /is-callable@1.2.7:
     resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
     engines: {node: '>= 0.4'}
-    dev: true
 
   /is-ci@3.0.1:
     resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==}
@@ -12906,11 +12824,6 @@ packages:
     resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
     engines: {node: '>=8'}
 
-  /is-fullwidth-code-point@4.0.0:
-    resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==}
-    engines: {node: '>=12'}
-    dev: true
-
   /is-generator-fn@2.1.0:
     resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==}
     engines: {node: '>=6'}
@@ -13182,7 +13095,7 @@ packages:
 
   /is@3.3.0:
     resolution: {integrity: sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==}
-    dev: true
+    dev: false
 
   /isarray@0.0.1:
     resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
@@ -13229,7 +13142,7 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.0
       semver: 6.3.0
@@ -13304,7 +13217,7 @@ packages:
       '@jest/expect': 29.5.0
       '@jest/test-result': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       chalk: 4.1.2
       co: 4.6.0
       dedent: 0.7.0
@@ -13324,7 +13237,7 @@ packages:
       - supports-color
     dev: true
 
-  /jest-cli@29.5.0(@types/node@18.15.11):
+  /jest-cli@29.5.0(@types/node@18.16.3):
     resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13341,7 +13254,7 @@ packages:
       exit: 0.1.2
       graceful-fs: 4.2.11
       import-local: 3.1.0
-      jest-config: 29.5.0(@types/node@18.15.11)
+      jest-config: 29.5.0(@types/node@18.16.3)
       jest-util: 29.5.0
       jest-validate: 29.5.0
       prompts: 2.4.2
@@ -13352,7 +13265,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest-config@29.5.0(@types/node@18.15.11):
+  /jest-config@29.5.0(@types/node@18.16.3):
     resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -13367,7 +13280,7 @@ packages:
       '@babel/core': 7.21.3
       '@jest/test-sequencer': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       babel-jest: 29.5.0(@babel/core@7.21.3)
       chalk: 4.1.2
       ci-info: 3.7.1
@@ -13436,7 +13349,7 @@ packages:
       '@jest/environment': 29.5.0
       '@jest/fake-timers': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       jest-mock: 29.5.0
       jest-util: 29.5.0
     dev: true
@@ -13466,7 +13379,7 @@ packages:
     dependencies:
       '@jest/types': 29.5.0
       '@types/graceful-fs': 4.1.6
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -13517,7 +13430,7 @@ packages:
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
       '@jest/types': 27.5.1
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
     dev: true
 
   /jest-mock@29.5.0:
@@ -13525,7 +13438,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       jest-util: 29.5.0
     dev: true
 
@@ -13580,7 +13493,7 @@ packages:
       '@jest/test-result': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -13611,7 +13524,7 @@ packages:
       '@jest/test-result': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       chalk: 4.1.2
       cjs-module-lexer: 1.2.2
       collect-v8-coverage: 1.0.1
@@ -13656,7 +13569,7 @@ packages:
       jest-util: 29.5.0
       natural-compare: 1.4.0
       pretty-format: 29.5.0
-      semver: 7.3.8
+      semver: 7.5.0
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -13666,7 +13579,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       chalk: 4.1.2
       ci-info: 3.7.1
       graceful-fs: 4.2.11
@@ -13691,7 +13604,7 @@ packages:
     dependencies:
       '@jest/test-result': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -13710,13 +13623,13 @@ packages:
     resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 18.15.11
+      '@types/node': 18.16.3
       jest-util: 29.5.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
 
-  /jest@29.5.0(@types/node@18.15.11):
+  /jest@29.5.0(@types/node@18.16.3):
     resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13729,7 +13642,7 @@ packages:
       '@jest/core': 29.5.0
       '@jest/types': 29.5.0
       import-local: 3.1.0
-      jest-cli: 29.5.0(@types/node@18.15.11)
+      jest-cli: 29.5.0(@types/node@18.16.3)
     transitivePeerDependencies:
       - '@types/node'
       - supports-color
@@ -13782,6 +13695,11 @@ packages:
     resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
     dev: true
 
+  /js-string-escape@1.0.1:
+    resolution: {integrity: sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==}
+    engines: {node: '>= 0.8'}
+    dev: true
+
   /js-stringify@1.0.2:
     resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
 
@@ -13795,7 +13713,6 @@ packages:
     dependencies:
       argparse: 1.0.10
       esprima: 4.0.1
-    dev: true
 
   /js-yaml@3.7.0:
     resolution: {integrity: sha512-eIlkGty7HGmntbV6P/ZlAsoncFLGsNoM27lkTzS+oneY/EiNhj+geqD9ezg/ip+SW6Var0BJU2JtV0vEUZpWVQ==}
@@ -13821,36 +13738,6 @@ packages:
     resolution: {integrity: sha512-lJH6tJ77V8Nzd5QWRkFYCLc13a3vADkh3r/Fi8HupZGWk2OVVDfnZP8V/VgQgZ+lzW0kG2UGb5hFgt3V3ndotQ==}
     engines: {node: '>=0.1.90'}
 
-  /jscodeshift@0.14.0(@babel/preset-env@7.20.2):
-    resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==}
-    hasBin: true
-    peerDependencies:
-      '@babel/preset-env': ^7.1.6
-    dependencies:
-      '@babel/core': 7.21.3
-      '@babel/parser': 7.21.3
-      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3)
-      '@babel/preset-env': 7.20.2(@babel/core@7.21.3)
-      '@babel/preset-flow': 7.18.6(@babel/core@7.21.3)
-      '@babel/preset-typescript': 7.21.0(@babel/core@7.21.3)
-      '@babel/register': 7.21.0(@babel/core@7.21.3)
-      babel-core: 7.0.0-bridge.0(@babel/core@7.21.3)
-      chalk: 4.1.2
-      flow-parser: 0.202.0
-      graceful-fs: 4.2.11
-      micromatch: 4.0.5
-      neo-async: 2.6.2
-      node-dir: 0.1.17
-      recast: 0.21.5
-      temp: 0.8.4
-      write-file-atomic: 2.4.3
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /jscodeshift@0.14.0(@babel/preset-env@7.21.4):
     resolution: {integrity: sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==}
     hasBin: true
@@ -13858,7 +13745,7 @@ packages:
       '@babel/preset-env': ^7.1.6
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3)
       '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3)
       '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3)
@@ -13945,7 +13832,7 @@ packages:
       object.assign: 4.1.4
       promiseback: 2.0.3
       safer-buffer: 2.1.2
-    dev: true
+    dev: false
 
   /json-parse-even-better-errors@2.3.1:
     resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@@ -13971,7 +13858,7 @@ packages:
     resolution: {integrity: sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==}
     hasBin: true
     dependencies:
-      minimist: 1.2.7
+      minimist: 1.2.8
     dev: true
 
   /json5@2.2.3:
@@ -14035,8 +13922,8 @@ packages:
       verror: 1.10.0
     dev: true
 
-  /jsrsasign@10.7.0:
-    resolution: {integrity: sha512-D5V2gGpYGtwbAtQHoglTVrpYf7QJuNoPEhaLOsTFONS2jXUl3qyR1hnYrNpASAybqQeiDYA3zGthR0ubgPRoQA==}
+  /jsrsasign@10.8.6:
+    resolution: {integrity: sha512-bQmbVtsfbgaKBTWCKiDCPlUPbdlRIK/FzSwT3BzIgZl/cU6TqXu6pZJsCI/dJVrZ9Gir5GC4woqw9shH/v7MBw==}
     dev: false
 
   /jssha@3.3.0:
@@ -14232,7 +14119,7 @@ packages:
       log-update: 4.0.0
       p-map: 4.0.0
       rfdc: 1.3.0
-      rxjs: 7.8.0
+      rxjs: 7.8.1
       through: 2.3.8
       wrap-ansi: 7.0.0
     dev: true
@@ -14255,7 +14142,6 @@ packages:
   /local-pkg@0.4.3:
     resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
     engines: {node: '>=14'}
-    dev: true
 
   /locate-path@3.0.0:
     resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
@@ -14280,15 +14166,15 @@ packages:
 
   /lodash.clone@4.5.0:
     resolution: {integrity: sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==}
-    dev: true
+    dev: false
 
   /lodash.clonedeep@4.5.0:
     resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==}
-    dev: true
+    dev: false
 
   /lodash.constant@3.0.0:
     resolution: {integrity: sha512-X5XMrB+SdI1mFa81162NSTo/YNd23SLdLOLzcXTwS4inDZ5YCL8X67UFzZJAH4CqIa6R8cr56CShfA5K5MFiYQ==}
-    dev: true
+    dev: false
 
   /lodash.debounce@4.0.8:
     resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==}
@@ -14303,11 +14189,11 @@ packages:
 
   /lodash.filter@4.6.0:
     resolution: {integrity: sha512-pXYUy7PR8BCLwX5mgJ/aNtyOvuJTdZAo9EQFUvMIYugqmJxnrYaANvTbgndOzHSCSR0wnlBBfRXJL5SbWxo3FQ==}
-    dev: true
+    dev: false
 
   /lodash.flatmap@4.5.0:
     resolution: {integrity: sha512-/OcpcAGWlrZyoHGeHh3cAoa6nGdX6QYtmzNP84Jqol6UEQQ2gIaU3H+0eICcjcKGl0/XF8LWOujNn9lffsnaOg==}
-    dev: true
+    dev: false
 
   /lodash.flatten@4.4.0:
     resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==}
@@ -14315,7 +14201,7 @@ packages:
 
   /lodash.foreach@4.5.0:
     resolution: {integrity: sha512-aEXTF4d+m05rVOAUG3z4vZZ4xVexLKZGF0lIxuHZ1Hplpk/3B6Z1+/ICICYRLm7c41Z2xiejbkCkJoTlypoXhQ==}
-    dev: true
+    dev: false
 
   /lodash.get@4.4.2:
     resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
@@ -14323,22 +14209,21 @@ packages:
 
   /lodash.has@4.5.2:
     resolution: {integrity: sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==}
-    dev: true
+    dev: false
 
   /lodash.isarguments@3.1.0:
     resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
 
   /lodash.isempty@4.4.0:
     resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==}
-    dev: true
+    dev: false
 
   /lodash.isequal@4.5.0:
     resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
-    dev: true
 
   /lodash.isfunction@3.0.9:
     resolution: {integrity: sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==}
-    dev: true
+    dev: false
 
   /lodash.isplainobject@4.0.6:
     resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
@@ -14346,15 +14231,15 @@ packages:
 
   /lodash.isundefined@3.0.1:
     resolution: {integrity: sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==}
-    dev: true
+    dev: false
 
   /lodash.keys@4.2.0:
     resolution: {integrity: sha512-J79MkJcp7Df5mizHiVNpjoHXLi4HLjh9VLS/M7lQSGoQ+0oQ+lWEigREkqKyizPB1IawvQLLKY8mzEcm1tkyxQ==}
-    dev: true
+    dev: false
 
   /lodash.map@4.6.0:
     resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==}
-    dev: true
+    dev: false
 
   /lodash.memoize@4.1.2:
     resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==}
@@ -14369,11 +14254,11 @@ packages:
 
   /lodash.reduce@4.6.0:
     resolution: {integrity: sha512-6raRe2vxCYBhpBu+B+TtNGUzah+hQjVdu3E17wfusjyrXBka2nBS8OH/gjVZ5PvHOhWmIZTYri09Z6n/QfnNMw==}
-    dev: true
+    dev: false
 
   /lodash.size@4.2.0:
     resolution: {integrity: sha512-wbu3SF1XC5ijqm0piNxw59yCbuUf2kaShumYBLWUrcCvwh6C8odz6SY/wGVzCWTQTFL/1Ygbvqg2eLtspUVVAQ==}
-    dev: true
+    dev: false
 
   /lodash.throttle@4.1.1:
     resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
@@ -14381,14 +14266,15 @@ packages:
 
   /lodash.topairs@4.3.0:
     resolution: {integrity: sha512-qrRMbykBSEGdOgQLJJqVSdPWMD7Q+GJJ5jMRfQYb+LTLsw3tYVIabnCzRqTJb2WTo17PG5gNzXuFaZgYH/9SAQ==}
-    dev: true
+    dev: false
 
   /lodash.transform@4.6.0:
     resolution: {integrity: sha512-LO37ZnhmBVx0GvOU/caQuipEh4GN82TcWv3yHlebGDgOxbxiwwzW5Pcx2AcvpIv2WmvmSMoC492yQFNhy/l/UQ==}
-    dev: true
+    dev: false
 
   /lodash.union@4.6.0:
     resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==}
+    dev: false
 
   /lodash.uniq@4.5.0:
     resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
@@ -14396,7 +14282,7 @@ packages:
 
   /lodash.values@4.3.0:
     resolution: {integrity: sha512-r0RwvdCv8id9TUblb/O7rYPwVy6lerCbcawrfdo9iC/1t1wsNMJknO79WNBgwkH0hIeJ08jmvvESbFpNb4jH0Q==}
-    dev: true
+    dev: false
 
   /lodash@4.17.21:
     resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
@@ -14439,6 +14325,7 @@ packages:
   /lowercase-keys@2.0.0:
     resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==}
     engines: {node: '>=8'}
+    dev: false
 
   /lowercase-keys@3.0.0:
     resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==}
@@ -14488,6 +14375,13 @@ packages:
     hasBin: true
     dev: true
 
+  /magic-string-ast@0.1.2:
+    resolution: {integrity: sha512-P53AZrzq7hclCU6HWj88xNZHmP15DKjMmK/vBytO1qnpYP3ul4IEZlyCE0aU3JRnmgWmZPmoTKj4Bls7v0pMyA==}
+    engines: {node: '>=14.19.0'}
+    dependencies:
+      magic-string: 0.30.0
+    dev: false
+
   /magic-string@0.25.9:
     resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==}
     dependencies:
@@ -14498,7 +14392,12 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.14
-    dev: true
+
+  /magic-string@0.30.0:
+    resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
+    engines: {node: '>=12'}
+    dependencies:
+      '@jridgewell/sourcemap-codec': 1.4.14
 
   /mailcheck@1.1.1:
     resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==}
@@ -14613,6 +14512,13 @@ packages:
     resolution: {integrity: sha512-v2huwvQGOHTGOkMqtHd2hercCG3f6QAObTisPPHg8TZqq2lz7eIY/5i/5YUV8Ibf3mEioFEmwibcPUF2/fnKKQ==}
     dev: false
 
+  /md5-hex@3.0.1:
+    resolution: {integrity: sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==}
+    engines: {node: '>=8'}
+    dependencies:
+      blueimp-md5: 2.19.0
+    dev: true
+
   /mdast-util-definitions@4.0.0:
     resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==}
     dependencies:
@@ -14628,6 +14534,14 @@ packages:
     engines: {node: '>= 0.6'}
     dev: true
 
+  /meilisearch@0.32.3:
+    resolution: {integrity: sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==}
+    dependencies:
+      cross-fetch: 3.1.5
+    transitivePeerDependencies:
+      - encoding
+    dev: false
+
   /memoizerific@1.11.3:
     resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==}
     dependencies:
@@ -14741,6 +14655,7 @@ packages:
   /mimic-response@1.0.1:
     resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==}
     engines: {node: '>=4'}
+    dev: false
 
   /mimic-response@3.1.0:
     resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
@@ -14770,9 +14685,9 @@ packages:
     dependencies:
       brace-expansion: 2.0.1
 
-  /minimatch@6.2.0:
-    resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==}
-    engines: {node: '>=10'}
+  /minimatch@9.0.0:
+    resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==}
+    engines: {node: '>=16 || 14 >=14.17'}
     dependencies:
       brace-expansion: 2.0.1
     dev: true
@@ -14788,6 +14703,10 @@ packages:
 
   /minimist@1.2.7:
     resolution: {integrity: sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==}
+    dev: false
+
+  /minimist@1.2.8:
+    resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
 
   /minipass-collect@1.0.2:
     resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
@@ -14874,7 +14793,7 @@ packages:
     resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
     hasBin: true
     dependencies:
-      minimist: 1.2.7
+      minimist: 1.2.8
 
   /mkdirp@1.0.4:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@@ -14887,8 +14806,8 @@ packages:
     hasBin: true
     dev: false
 
-  /mlly@1.1.1:
-    resolution: {integrity: sha512-Jnlh4W/aI4GySPo6+DyTN17Q75KKbLTyFK8BrGhjNP4rxuUjbRWhE6gHg3bs33URWAF44FRm7gdQA348i3XxRw==}
+  /mlly@1.2.0:
+    resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==}
     dependencies:
       acorn: 8.8.2
       pathe: 1.1.0
@@ -14955,10 +14874,10 @@ packages:
       msw: '>=0.35.0 <2.0.0'
     dependencies:
       is-node-process: 1.0.1
-      msw: 1.2.1(typescript@5.0.3)
+      msw: 1.2.1(typescript@5.0.4)
     dev: true
 
-  /msw@1.2.1(typescript@5.0.3):
+  /msw@1.2.1(typescript@5.0.4):
     resolution: {integrity: sha512-bF7qWJQSmKn6bwGYVPXOxhexTCGD5oJSZg8yt8IBClxvo3Dx/1W0zqE1nX9BSWmzRsCKWfeGWcB/vpqV6aclpw==}
     engines: {node: '>=14'}
     hasBin: true
@@ -14987,7 +14906,7 @@ packages:
       path-to-regexp: 6.2.1
       strict-event-emitter: 0.4.6
       type-fest: 2.19.0
-      typescript: 5.0.3
+      typescript: 5.0.4
       yargs: 17.6.2
     transitivePeerDependencies:
       - encoding
@@ -15033,6 +14952,11 @@ packages:
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
+  /nanoid@3.3.6:
+    resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
+    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+    hasBin: true
+
   /nanomatch@1.2.13:
     resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
     engines: {node: '>=0.10.0'}
@@ -15053,7 +14977,6 @@ packages:
 
   /napi-build-utils@1.0.2:
     resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
-    dev: false
 
   /natural-compare-lite@1.4.0:
     resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
@@ -15135,16 +15058,14 @@ packages:
     resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==}
     engines: {node: '>=10'}
     dependencies:
-      semver: 7.3.8
-    dev: false
+      semver: 7.5.0
 
   /node-addon-api@5.0.0:
     resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==}
     dev: false
 
-  /node-addon-api@6.0.0:
-    resolution: {integrity: sha512-GyHvgPvUXBvAkXa0YvYnhilSB1A+FRYMpIVggKzPZqdaZfevZOuzfWzyvgzOwRLHBeo/MMswmJFsrNF4Nw1pmA==}
-    dev: false
+  /node-addon-api@6.1.0:
+    resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
 
   /node-bitmap@0.0.1:
     resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==}
@@ -15204,7 +15125,7 @@ packages:
       nopt: 6.0.0
       npmlog: 6.0.2
       rimraf: 3.0.2
-      semver: 7.3.8
+      semver: 7.5.0
       tar: 6.1.13
       which: 2.0.2
     transitivePeerDependencies:
@@ -15226,7 +15147,7 @@ packages:
     dependencies:
       has: 1.0.3
       is: 3.3.0
-    dev: true
+    dev: false
 
   /nodemailer@6.9.1:
     resolution: {integrity: sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==}
@@ -15267,7 +15188,7 @@ packages:
     dependencies:
       hosted-git-info: 4.1.0
       is-core-module: 2.11.0
-      semver: 7.3.8
+      semver: 7.5.0
       validate-npm-package-license: 3.0.4
     dev: true
 
@@ -15300,6 +15221,7 @@ packages:
   /normalize-url@6.1.0:
     resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==}
     engines: {node: '>=10'}
+    dev: false
 
   /normalize-url@8.0.0:
     resolution: {integrity: sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==}
@@ -15350,13 +15272,13 @@ packages:
       set-blocking: 2.0.0
     dev: false
 
-  /nsfwjs@2.4.2(@tensorflow/tfjs@4.2.0):
+  /nsfwjs@2.4.2(@tensorflow/tfjs@4.4.0):
     resolution: {integrity: sha512-i4Pp2yt59qPQgeZFyg3wXFBX52uSeu/hkDoqdZfe+sILRxNBUu0VDogj7Lmqak0GlrXviS/wLiVeIx40IDUu7A==}
     peerDependencies:
       '@tensorflow/tfjs': ^3.18.0
     dependencies:
       '@nsfw-filter/gif-frames': 1.0.2
-      '@tensorflow/tfjs': 4.2.0(seedrandom@3.0.5)
+      '@tensorflow/tfjs': 4.4.0(seedrandom@3.0.5)
     dev: false
 
   /nth-check@2.1.1:
@@ -15404,7 +15326,7 @@ packages:
   /object-hash@3.0.0:
     resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
     engines: {node: '>= 6'}
-    dev: true
+    dev: false
 
   /object-inspect@1.12.2:
     resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==}
@@ -15510,7 +15432,7 @@ packages:
     resolution: {integrity: sha512-6vaNInhu+CHxtONf3zw3vq4SP2DOQhjBvIa3rNcG0+P7eKWlYH6Peu7rHizSloRU2EwMz6GraLieis9Ac9+p1w==}
     dependencies:
       wrappy: 1.0.2
-    dev: true
+    dev: false
 
   /once@1.4.0:
     resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -15635,6 +15557,7 @@ packages:
   /p-cancelable@2.1.1:
     resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==}
     engines: {node: '>=8'}
+    dev: false
 
   /p-cancelable@3.0.0:
     resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==}
@@ -15684,11 +15607,6 @@ packages:
       p-limit: 3.1.0
     dev: true
 
-  /p-map@2.1.0:
-    resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}
-    engines: {node: '>=6'}
-    dev: false
-
   /p-map@4.0.0:
     resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
     engines: {node: '>=10'}
@@ -15716,7 +15634,7 @@ packages:
 
   /packageurl-js@1.0.1:
     resolution: {integrity: sha512-EtXC0kgLjy/C7S4SN3Kk1SDRWLzIn/LUK0gXlz3gsxDdpI0k7q8C3SASpV0pc+v0yADBTt5rWewq/flGdGxtoQ==}
-    dev: true
+    dev: false
 
   /packet-reader@1.0.0:
     resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
@@ -16091,7 +16009,7 @@ packages:
     resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==}
     dependencies:
       jsonc-parser: 3.2.0
-      mlly: 1.1.1
+      mlly: 1.2.0
       pathe: 1.1.0
     dev: true
 
@@ -16121,7 +16039,7 @@ packages:
   /pluralize@7.0.0:
     resolution: {integrity: sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==}
     engines: {node: '>=4'}
-    dev: true
+    dev: false
 
   /pngjs-nozlib@1.0.0:
     resolution: {integrity: sha512-N1PggqLp9xDqwAoKvGohmZ3m4/N9xpY0nDZivFqQLcpLHmliHnCp9BuNCsOeqHWMuEEgFjpEaq9dZq6RZyy0fA==}
@@ -16376,6 +16294,14 @@ packages:
       picocolors: 1.0.0
       source-map-js: 1.0.2
 
+  /postcss@8.4.23:
+    resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
+    engines: {node: ^10 || ^12 || >=14}
+    dependencies:
+      nanoid: 3.3.6
+      picocolors: 1.0.0
+      source-map-js: 1.0.2
+
   /postgres-array@2.0.0:
     resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
     engines: {node: '>=4'}
@@ -16402,7 +16328,7 @@ packages:
       detect-libc: 2.0.1
       expand-template: 2.0.3
       github-from-package: 0.0.0
-      minimist: 1.2.7
+      minimist: 1.2.8
       mkdirp-classic: 0.5.3
       napi-build-utils: 1.0.2
       node-abi: 3.31.0
@@ -16411,7 +16337,6 @@ packages:
       simple-get: 4.0.1
       tar-fs: 2.1.1
       tunnel-agent: 0.6.0
-    dev: false
 
   /prelude-ls@1.1.2:
     resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
@@ -16427,8 +16352,8 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: false
 
-  /prettier@2.8.7:
-    resolution: {integrity: sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==}
+  /prettier@2.8.8:
+    resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
     engines: {node: '>=10.13.0'}
     hasBin: true
     dev: true
@@ -16436,7 +16361,6 @@ packages:
   /pretty-bytes@5.6.0:
     resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
     engines: {node: '>=6'}
-    dev: true
 
   /pretty-format@27.5.1:
     resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
@@ -16527,7 +16451,7 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       promise: 7.3.1
-    dev: true
+    dev: false
 
   /promise-inflight@1.0.1:
     resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
@@ -16565,7 +16489,7 @@ packages:
     dependencies:
       is-callable: 1.2.7
       promise-deferred: 2.0.3
-    dev: true
+    dev: false
 
   /prompts@2.4.2:
     resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
@@ -16758,9 +16682,10 @@ packages:
     engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
     dev: false
 
-  /qrcode@1.5.1:
-    resolution: {integrity: sha512-nS8NJ1Z3md8uTjKtP+SGGhfqmTCs5flU/xR623oI0JX+Wepz9R8UrRVCTBTJm3qGw3rH6jJ6MUHjkDx15cxSSg==}
+  /qrcode@1.5.3:
+    resolution: {integrity: sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==}
     engines: {node: '>=10.13.0'}
+    hasBin: true
     dependencies:
       dijkstrajs: 1.0.2
       encode-utf8: 1.0.3
@@ -16875,9 +16800,8 @@ packages:
     dependencies:
       deep-extend: 0.6.0
       ini: 1.3.8
-      minimist: 1.2.7
+      minimist: 1.2.8
       strip-json-comments: 2.0.1
-    dev: false
 
   /rdf-canonize@3.3.0:
     resolution: {integrity: sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==}
@@ -16908,12 +16832,12 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /react-docgen-typescript@2.2.2(typescript@5.0.3):
+  /react-docgen-typescript@2.2.2(typescript@5.0.4):
     resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
     peerDependencies:
       typescript: '>= 4.3.x'
     dependencies:
-      typescript: 5.0.3
+      typescript: 5.0.4
     dev: true
 
   /react-docgen@6.0.0-alpha.3:
@@ -17155,10 +17079,6 @@ packages:
       strip-indent: 3.0.0
     dev: true
 
-  /redis-commands@1.7.0:
-    resolution: {integrity: sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==}
-    dev: false
-
   /redis-errors@1.2.0:
     resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
     engines: {node: '>=4'}
@@ -17464,6 +17384,7 @@ packages:
     resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==}
     dependencies:
       lowercase-keys: 2.0.0
+    dev: false
 
   /responselike@3.0.0:
     resolution: {integrity: sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==}
@@ -17526,8 +17447,8 @@ packages:
       seedrandom: 2.4.2
     dev: false
 
-  /rollup@3.20.2:
-    resolution: {integrity: sha512-3zwkBQl7Ai7MFYQE0y1MeQ15+9jsi7XxfrqwTb/9EK8D9C9+//EBR4M+CuA1KODRaNbFez/lWxA5vhEGZp4MUg==}
+  /rollup@3.21.3:
+    resolution: {integrity: sha512-VnPfEG51nIv2xPLnZaekkuN06q9ZbnyDcLkaBdJa/W7UddyhOfMP2yOPziYQfeY7k++fZM8FdQIummFN5y14kA==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -17537,11 +17458,11 @@ packages:
     resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==}
     dev: false
 
-  /rss-parser@3.12.0:
-    resolution: {integrity: sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A==}
+  /rss-parser@3.13.0:
+    resolution: {integrity: sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==}
     dependencies:
       entities: 2.2.0
-      xml2js: 0.4.23
+      xml2js: 0.5.0
     dev: false
 
   /run-async@2.4.1:
@@ -17554,8 +17475,8 @@ packages:
     dependencies:
       queue-microtask: 1.2.3
 
-  /rxjs@7.8.0:
-    resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==}
+  /rxjs@7.8.1:
+    resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==}
     dependencies:
       tslib: 2.5.0
 
@@ -17615,9 +17536,9 @@ packages:
       postcss: 8.4.21
     dev: false
 
-  /sass@1.60.0:
-    resolution: {integrity: sha512-updbwW6fNb5gGm8qMXzVO7V4sWf7LMXnMly/JEyfbfERbVH46Fn6q02BX7/eHTdKpE7d+oTkMMQpFWNUMfFbgQ==}
-    engines: {node: '>=12.0.0'}
+  /sass@1.62.1:
+    resolution: {integrity: sha512-NHpxIzN29MXvWiuswfc1W3I0N8SXBd8UR26WntmDlRYf0bSADnwnOjsyMZ3lMezSlArD33Vs3YFhp7dWvL770A==}
+    engines: {node: '>=14.0.0'}
     hasBin: true
     dependencies:
       chokidar: 3.5.3
@@ -17691,6 +17612,14 @@ packages:
     hasBin: true
     dependencies:
       lru-cache: 6.0.0
+    dev: true
+
+  /semver@7.5.0:
+    resolution: {integrity: sha512-+XC0AD/R7Q2mPSRuy2Id0+CGTZ98+8f+KvwirxOKIEyid+XSx6HbC63p+O4IndTHuX5Z+JxQ0TghCkO5Cg/2HA==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      lru-cache: 6.0.0
 
   /send@0.18.0:
     resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
@@ -17782,26 +17711,25 @@ packages:
       detect-libc: 2.0.1
       node-addon-api: 5.0.0
       prebuild-install: 7.1.1
-      semver: 7.3.8
+      semver: 7.5.0
       simple-get: 4.0.1
       tar-fs: 2.1.1
       tunnel-agent: 0.6.0
     dev: false
 
-  /sharp@0.32.0:
-    resolution: {integrity: sha512-yLAypVcqj1toSAqRSwbs86nEzfyZVDYqjuUX8grhFpeij0DDNagKJXELS/auegDBRDg1XBtELdOGfo2X1cCpeA==}
+  /sharp@0.32.1:
+    resolution: {integrity: sha512-kQTFtj7ldpUqSe8kDxoGLZc1rnMFU0AO2pqbX6pLy3b7Oj8ivJIdoKNwxHVQG2HN6XpHPJqCSM2nsma2gOXvOg==}
     engines: {node: '>=14.15.0'}
     requiresBuild: true
     dependencies:
       color: 4.2.3
       detect-libc: 2.0.1
-      node-addon-api: 6.0.0
+      node-addon-api: 6.1.0
       prebuild-install: 7.1.1
-      semver: 7.3.8
+      semver: 7.5.0
       simple-get: 4.0.1
       tar-fs: 2.1.1
       tunnel-agent: 0.6.0
-    dev: false
 
   /shebang-command@1.2.0:
     resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
@@ -17856,7 +17784,6 @@ packages:
 
   /simple-concat@1.0.1:
     resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
-    dev: false
 
   /simple-get@4.0.1:
     resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
@@ -17864,13 +17791,11 @@ packages:
       decompress-response: 6.0.0
       once: 1.4.0
       simple-concat: 1.0.1
-    dev: false
 
   /simple-swizzle@0.2.2:
     resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
     dependencies:
       is-arrayish: 0.3.2
-    dev: false
 
   /simple-update-notifier@1.1.0:
     resolution: {integrity: sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==}
@@ -17894,6 +17819,121 @@ packages:
     resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
     dev: true
 
+  /slacc-android-arm-eabi@0.0.7:
+    resolution: {integrity: sha512-6TikZlR1jsQscxwphhrf0U4xbsRy6zKJ0zmEULopTzbohgo5OLdZ7L3tQazkYlaaFe3YjGnVLW3FfGhhrajVog==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-android-arm64@0.0.7:
+    resolution: {integrity: sha512-aol/9Rg0Hfqu81hpK+HXcx9sGYu4qqYU+djBCgLtb8I6ZMdWUdE0dp8ACBoTOmYn34hYGcUu4FlJUZ8r7Utucg==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [android]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-darwin-arm64@0.0.7:
+    resolution: {integrity: sha512-PkV7rO/c9AImNYDacP+kxtOjVuxjy06IIOAxbWerIWvoeqsCNRtiF/dh+OqIACRFBuHIDe0oAyUCEMGUTnzjyQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-darwin-universal@0.0.7:
+    resolution: {integrity: sha512-Y9zXpL40m4Yq3dE5vdnAgfmn0Fxc0Bf0ixC9TSl96gKeIZEd6drkjfpHFdsIDNImzOksIAUo0HHiDdbEfE7zdQ==}
+    engines: {node: '>= 10'}
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-darwin-x64@0.0.7:
+    resolution: {integrity: sha512-yKaGjX2YJl1QHe4NgqQVsY83jees3hjFxEUPoKpuZEQzWbMNn0XSyceFRGXIk1oDqiKU40UcsdcCedjYjSEd0Q==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-linux-arm-gnueabihf@0.0.7:
+    resolution: {integrity: sha512-pdWMdQeX6uA9JfSoWo9EHH0yRiwXKMbaKoS9gflDSyt/hjeR3Qx/KK7Wihd7HeXx7njlNdpr9ycTRmm5NgapQQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-linux-arm64-gnu@0.0.7:
+    resolution: {integrity: sha512-hz9TK/w6fxeNZXyFzuLq5cJD/XRyJbo6BaIdW+VrKKnb9nkLnWlqDQtdtJk7Fw7zHjdY3Uqufjwm0iT6qBVpUQ==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-linux-arm64-musl@0.0.7:
+    resolution: {integrity: sha512-wCDAYL7e+lh3XL7g87Ui/Bb2Ap9GcBqeJuj2yHIx6MYC8ontwFSXhqRTmd2zmPLmZA5Nc11aKGN11YNu0Pnwlw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-linux-x64-gnu@0.0.7:
+    resolution: {integrity: sha512-E5+2cveizpfHXCk/Hu5VfslWFeDVw47nywODiJ8CsofT2l5ITfYPMFEBXm9ORY25mGBTgsO6lJYiF9Hz4FlS9Q==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-win32-arm64-msvc@0.0.7:
+    resolution: {integrity: sha512-3a+qnkZbP+Pr5RZuzd0Vi1uCal137QiJajRAWT4r7qwu+Zidd50x2oikQ4rAegqZVTm8qTwVmWA+WmH8WHI7iw==}
+    engines: {node: '>= 10'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-win32-x64-msvc@0.0.7:
+    resolution: {integrity: sha512-ydFdZ7wEXQPsw2Tg+yG9uJdCGTehyPtrWBVUMa7fojr3j1gbtThXS2l9Ad/6fYYi2VwdaYPLWbwV3GYElPGL8g==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc@0.0.7:
+    resolution: {integrity: sha512-rwi2F3oJaGPST9JdCoUd5fnSZaoZFgTL00GFKhKufT48uwtUEAHlOL0t8gEVmon71X+53f9nEdsGWhwtOutJTQ==}
+    engines: {node: '>= 10'}
+    optionalDependencies:
+      slacc-android-arm-eabi: 0.0.7
+      slacc-android-arm64: 0.0.7
+      slacc-darwin-arm64: 0.0.7
+      slacc-darwin-universal: 0.0.7
+      slacc-darwin-x64: 0.0.7
+      slacc-linux-arm-gnueabihf: 0.0.7
+      slacc-linux-arm64-gnu: 0.0.7
+      slacc-linux-arm64-musl: 0.0.7
+      slacc-linux-x64-gnu: 0.0.7
+      slacc-win32-arm64-msvc: 0.0.7
+      slacc-win32-x64-msvc: 0.0.7
+    dev: false
+
   /slash@3.0.0:
     resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
     engines: {node: '>=8'}
@@ -17921,14 +17961,6 @@ packages:
       is-fullwidth-code-point: 3.0.0
     dev: true
 
-  /slice-ansi@5.0.0:
-    resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
-    engines: {node: '>=12'}
-    dependencies:
-      ansi-styles: 6.2.1
-      is-fullwidth-code-point: 4.0.0
-    dev: true
-
   /smart-buffer@4.2.0:
     resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
     engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
@@ -17969,10 +18001,10 @@ packages:
       async: 3.2.4
       debug: 4.3.4(supports-color@8.1.1)
       lodash.merge: 4.6.2
-      minimist: 1.2.7
+      minimist: 1.2.8
     transitivePeerDependencies:
       - supports-color
-    dev: true
+    dev: false
 
   /snyk-nodejs-lockfile-parser@1.48.2:
     resolution: {integrity: sha512-CiuKigz4Ed/prR61T2hDDNnvqCr9JA4zTWE9xD4x+emt2zUVRHKF2RBKq2s82RA8jcts1OOXnREr1v1OvNxIpg==}
@@ -17990,13 +18022,13 @@ packages:
       lodash.isempty: 4.4.0
       lodash.topairs: 4.3.0
       micromatch: 4.0.5
-      semver: 7.3.8
+      semver: 7.5.0
       snyk-config: 5.1.0
       tslib: 1.14.1
       uuid: 8.3.2
     transitivePeerDependencies:
       - supports-color
-    dev: true
+    dev: false
 
   /socks-proxy-agent@7.0.0:
     resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==}
@@ -18221,11 +18253,11 @@ packages:
     resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==}
     dev: true
 
-  /storybook@7.0.2:
-    resolution: {integrity: sha512-/XBLhT9Vb14yNBcA9rlW15y+C6IsCA3kx5PKvK9kL10sKCi8invcY94UfCSisXe8HqsO3u6peumo2xpYucKMjw==}
+  /storybook@7.0.7:
+    resolution: {integrity: sha512-MaFAhpPm/KsaoIQfGzapnRyXNh1VbS8l38BNZR5ZD97ejGkLukJ7TO4fFS87Hyy6whAXo6tTdtqeCByMQ9gRFA==}
     hasBin: true
     dependencies:
-      '@storybook/cli': 7.0.2
+      '@storybook/cli': 7.0.7
     transitivePeerDependencies:
       - bufferutil
       - encoding
@@ -18243,7 +18275,7 @@ packages:
   /stream-buffers@3.0.2:
     resolution: {integrity: sha512-DQi1h8VEBA/lURbSwFtEHnSTb9s2/pwLEaFuNhXwy1Dx3Sa0lOuYT2yNUr4/j2fs8oCAMANtrZ5OrPZtyVs3MQ==}
     engines: {node: '>= 0.10.0'}
-    dev: true
+    dev: false
 
   /stream-combiner@0.0.4:
     resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==}
@@ -18269,7 +18301,7 @@ packages:
     resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==}
     dependencies:
       any-promise: 1.3.0
-    dev: true
+    dev: false
 
   /stream-to-promise@2.2.0:
     resolution: {integrity: sha512-HAGUASw8NT0k8JvIVutB2Y/9iBk7gpgEyAudXwNJmZERdMITGdajOa4VJfD/kNiA3TppQpTP4J+CtcHwdzKBAw==}
@@ -18277,7 +18309,7 @@ packages:
       any-promise: 1.3.0
       end-of-stream: 1.1.0
       stream-to-array: 2.3.0
-    dev: true
+    dev: false
 
   /stream-wormhole@1.1.0:
     resolution: {integrity: sha512-gHFfL3px0Kctd6Po0M8TzEvt3De/xu6cnRrjlfYNhwbhLPLwigI2t1nc6jrzNuaYg5C4YF78PPFuQPzRiqn9ew==}
@@ -18347,15 +18379,6 @@ packages:
       is-fullwidth-code-point: 3.0.0
       strip-ansi: 6.0.1
 
-  /string-width@5.1.2:
-    resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
-    engines: {node: '>=12'}
-    dependencies:
-      eastasianwidth: 0.2.0
-      emoji-regex: 9.2.2
-      strip-ansi: 7.0.1
-    dev: true
-
   /string.prototype.trimend@1.0.6:
     resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==}
     dependencies:
@@ -18412,13 +18435,6 @@ packages:
     dependencies:
       ansi-regex: 5.0.1
 
-  /strip-ansi@7.0.1:
-    resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==}
-    engines: {node: '>=12'}
-    dependencies:
-      ansi-regex: 6.0.1
-    dev: true
-
   /strip-bom@2.0.0:
     resolution: {integrity: sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==}
     engines: {node: '>=0.10.0'}
@@ -18459,7 +18475,6 @@ packages:
   /strip-json-comments@2.0.1:
     resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
     engines: {node: '>=0.10.0'}
-    dev: false
 
   /strip-json-comments@3.1.1:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
@@ -18742,11 +18757,21 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: false
 
+  /time-zone@1.0.0:
+    resolution: {integrity: sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==}
+    engines: {node: '>=4'}
+    dev: true
+
   /tiny-lru@10.0.1:
     resolution: {integrity: sha512-Vst+6kEsWvb17Zpz14sRJV/f8bUWKhqm6Dc+v08iShmIJ/WxqWytHzCTd6m88pS33rE2zpX34TRmOpAJPloNCA==}
     engines: {node: '>=6'}
     dev: false
 
+  /tiny-lru@11.0.1:
+    resolution: {integrity: sha512-iNgFugVuQgBKrqeO/mpiTTgmBsTP0WL6yeuLfLs/Ctf0pI/ixGqIRm8sDCwMcXGe9WWvt2sGXI5mNqZbValmJg==}
+    engines: {node: '>=12'}
+    dev: false
+
   /tinybench@2.4.0:
     resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==}
     dev: true
@@ -18760,8 +18785,8 @@ packages:
     engines: {node: '>=14.0.0'}
     dev: true
 
-  /tinyspy@1.1.1:
-    resolution: {integrity: sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g==}
+  /tinyspy@2.1.0:
+    resolution: {integrity: sha512-7eORpyqImoOvkQJCSkL0d0mB4NHHIFAy4b1u8PHdDa7SjGS2njzl6/lyGoZLm+eyYEtlUmFGE0rFj66SWxZgQQ==}
     engines: {node: '>=14.0.0'}
     dev: true
 
@@ -18885,7 +18910,7 @@ packages:
   /treeify@1.1.0:
     resolution: {integrity: sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==}
     engines: {node: '>=0.6'}
-    dev: true
+    dev: false
 
   /trim-newlines@3.0.1:
     resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
@@ -18908,8 +18933,8 @@ packages:
     resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==}
     dev: true
 
-  /tsc-alias@1.8.5:
-    resolution: {integrity: sha512-Y3ka0olwSRdbHPyX5kXhYY2aoBKuT53DFdeY+PpQUR4hg5M/b8eIRmC8dL4FBdd0wT366iWc6iDUUGe6QwI7mg==}
+  /tsc-alias@1.8.6:
+    resolution: {integrity: sha512-vq+i6VpE83IeMsSJVcFN03ZBofADhr8/gIJXjxpbnTRfN/MFXy0+SBaKG2o7p95QqXBGkeG98HYz3IkOOveFbg==}
     hasBin: true
     dependencies:
       chokidar: 3.5.3
@@ -18925,7 +18950,7 @@ packages:
     dependencies:
       '@types/json5': 0.0.29
       json5: 1.0.1
-      minimist: 1.2.7
+      minimist: 1.2.8
       strip-bom: 3.0.0
     dev: true
 
@@ -18958,14 +18983,14 @@ packages:
   /tslib@2.5.0:
     resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
 
-  /tsutils@3.21.0(typescript@5.0.3):
+  /tsutils@3.21.0(typescript@5.0.4):
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
     engines: {node: '>= 6'}
     peerDependencies:
       typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
     dependencies:
       tslib: 1.14.1
-      typescript: 5.0.3
+      typescript: 5.0.4
     dev: true
 
   /tunnel-agent@0.6.0:
@@ -18976,7 +19001,7 @@ packages:
   /tunnel@0.0.6:
     resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
     engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
-    dev: true
+    dev: false
 
   /tweetnacl@0.14.5:
     resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
@@ -19062,8 +19087,8 @@ packages:
   /typedarray@0.0.6:
     resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
 
-  /typeorm@0.3.13(ioredis@4.28.5)(pg@8.10.0):
-    resolution: {integrity: sha512-AqyKG4xtYST2zpgX/LdAleP8WAjwyO7EemRtG67EmmbFpWsPBk9BVr87wkN59huBned3qPEehVOSUQ81pNuH8A==}
+  /typeorm@0.3.15(ioredis@5.3.2)(pg@8.10.0):
+    resolution: {integrity: sha512-R4JSw8QjDP1W+ypeRz/XrCXIqubrLSnNAzJAp9EQSQIPHTv+YmUHZis8g08lOwFpuhqL9m8jkPSz8GWEKlU/ow==}
     engines: {node: '>= 12.9.0'}
     hasBin: true
     peerDependencies:
@@ -19072,7 +19097,7 @@ packages:
       better-sqlite3: ^7.1.2 || ^8.0.0
       hdb-pool: ^0.1.6
       ioredis: ^5.0.4
-      mongodb: ^3.6.0
+      mongodb: ^5.2.0
       mssql: ^9.1.1
       mysql2: ^2.2.5 || ^3.0.1
       oracledb: ^5.1.0
@@ -19128,15 +19153,13 @@ packages:
       debug: 4.3.4(supports-color@8.1.1)
       dotenv: 16.0.3
       glob: 8.1.0
-      ioredis: 4.28.5
-      js-yaml: 4.1.0
+      ioredis: 5.3.2
       mkdirp: 2.1.6
       pg: 8.10.0
       reflect-metadata: 0.1.13
       sha.js: 2.4.11
       tslib: 2.5.0
       uuid: 9.0.0
-      xml2js: 0.4.23
       yargs: 17.6.2
     transitivePeerDependencies:
       - supports-color
@@ -19148,8 +19171,8 @@ packages:
     hasBin: true
     dev: true
 
-  /typescript@5.0.3:
-    resolution: {integrity: sha512-xv8mOEDnigb/tN9PSMTwSEqAnUvkoXMQlicOb0IUVDBSQCgBSaAAROUZYy2IcUy5qU6XajK5jjjO7TMWqBTKZA==}
+  /typescript@5.0.4:
+    resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
     engines: {node: '>=12.20'}
     hasBin: true
 
@@ -19348,6 +19371,15 @@ packages:
       webpack-virtual-modules: 0.4.6
     dev: true
 
+  /unplugin@1.3.1:
+    resolution: {integrity: sha512-h4uUTIvFBQRxUKS2Wjys6ivoeofGhxzTe2sRWlooyjHXVttcVfV/JiavNd3d4+jty0SVV0dxGw9AkY9MwiaCEw==}
+    dependencies:
+      acorn: 8.8.2
+      chokidar: 3.5.3
+      webpack-sources: 3.2.3
+      webpack-virtual-modules: 0.5.0
+    dev: false
+
   /unset-value@1.0.0:
     resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
     engines: {node: '>=0.10.0'}
@@ -19451,10 +19483,6 @@ packages:
     engines: {node: '>= 0.4.0'}
     dev: true
 
-  /uuid-browser@3.1.0:
-    resolution: {integrity: sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg==}
-    dev: true
-
   /uuid@3.4.0:
     resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==}
     deprecated: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.
@@ -19467,7 +19495,6 @@ packages:
 
   /uuid@9.0.0:
     resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
-    dev: false
 
   /v8-to-istanbul@9.0.1:
     resolution: {integrity: sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==}
@@ -19575,17 +19602,17 @@ packages:
       replace-ext: 1.0.1
     dev: false
 
-  /vite-node@0.29.8(@types/node@18.15.11)(sass@1.60.0):
-    resolution: {integrity: sha512-b6OtCXfk65L6SElVM20q5G546yu10/kNrhg08afEoWlFRJXFq9/6glsvSVY+aI6YeC1tu2TtAqI2jHEQmOmsFw==}
-    engines: {node: '>=v14.16.0'}
+  /vite-node@0.30.1(@types/node@18.16.3)(sass@1.62.1):
+    resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==}
+    engines: {node: '>=v14.18.0'}
     hasBin: true
     dependencies:
       cac: 6.7.14
       debug: 4.3.4(supports-color@8.1.1)
-      mlly: 1.1.1
+      mlly: 1.2.0
       pathe: 1.1.0
       picocolors: 1.0.0
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19596,12 +19623,12 @@ packages:
       - terser
     dev: true
 
-  /vite-plugin-turbosnap@1.0.1:
-    resolution: {integrity: sha512-isVvISdXZyflIsXYrpTMBnyrtZq92ftohL8/xHi1H0kUwXIFDegqedX1kCKIQ04tjUkphB0cFbGzuvOGVwVTnQ==}
+  /vite-plugin-turbosnap@1.0.2:
+    resolution: {integrity: sha512-irjKcKXRn7v5bPAg4mAbsS6DgibpP1VUFL9tlgxU6lloK6V9yw9qCZkS+s2PtbkZpWNzr3TN3zVJAc6J7gJZmA==}
     dev: true
 
-  /vite@4.2.1(@types/node@18.15.11)(sass@1.60.0):
-    resolution: {integrity: sha512-7MKhqdy0ISo4wnvwtqZkjke6XN4taqQ2TBaTccLIpOKv7Vp2h4Y+NpmWCnGDeSvvn45KxvWgGyb0MkHvY1vgbg==}
+  /vite@4.3.4(@types/node@18.16.3)(sass@1.62.1):
+    resolution: {integrity: sha512-f90aqGBoxSFxWph2b39ae2uHAxm5jFBBdnfueNxZAT1FTpM13ccFQExCaKbR2xFW5atowjleRniQ7onjJ22QEg==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
@@ -19625,30 +19652,29 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 18.15.11
-      esbuild: 0.17.14
-      postcss: 8.4.21
-      resolve: 1.22.1
-      rollup: 3.20.2
-      sass: 1.60.0
+      '@types/node': 18.16.3
+      esbuild: 0.17.18
+      postcss: 8.4.23
+      rollup: 3.21.3
+      sass: 1.62.1
     optionalDependencies:
       fsevents: 2.3.2
 
-  /vitest-fetch-mock@0.2.2(vitest@0.29.8):
+  /vitest-fetch-mock@0.2.2(vitest@0.30.1):
     resolution: {integrity: sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==}
     engines: {node: '>=14.14.0'}
     peerDependencies:
       vitest: '>=0.16.0'
     dependencies:
       cross-fetch: 3.1.5
-      vitest: 0.29.8(happy-dom@8.9.0)(sass@1.60.0)
+      vitest: 0.30.1(happy-dom@9.10.2)(sass@1.62.1)
     transitivePeerDependencies:
       - encoding
     dev: true
 
-  /vitest@0.29.8(happy-dom@8.9.0)(sass@1.60.0):
-    resolution: {integrity: sha512-JIAVi2GK5cvA6awGpH0HvH/gEG9PZ0a/WoxdiV3PmqK+3CjQMf8c+J/Vhv4mdZ2nRyXFw66sAg6qz7VNkaHfDQ==}
-    engines: {node: '>=v14.16.0'}
+  /vitest@0.30.1(happy-dom@9.10.2)(sass@1.62.1):
+    resolution: {integrity: sha512-y35WTrSTlTxfMLttgQk4rHcaDkbHQwDP++SNwPb+7H8yb13Q3cu2EixrtHzF27iZ8v0XCciSsLg00RkPAzB/aA==}
+    engines: {node: '>=v14.18.0'}
     hasBin: true
     peerDependencies:
       '@edge-runtime/vm': '*'
@@ -19679,18 +19705,21 @@ packages:
     dependencies:
       '@types/chai': 4.3.4
       '@types/chai-subset': 1.3.3
-      '@types/node': 18.15.11
-      '@vitest/expect': 0.29.8
-      '@vitest/runner': 0.29.8
-      '@vitest/spy': 0.29.8
-      '@vitest/utils': 0.29.8
+      '@types/node': 18.16.3
+      '@vitest/expect': 0.30.1
+      '@vitest/runner': 0.30.1
+      '@vitest/snapshot': 0.30.1
+      '@vitest/spy': 0.30.1
+      '@vitest/utils': 0.30.1
       acorn: 8.8.2
       acorn-walk: 8.2.0
       cac: 6.7.14
       chai: 4.3.7
+      concordance: 5.0.4
       debug: 4.3.4(supports-color@8.1.1)
-      happy-dom: 8.9.0
+      happy-dom: 9.10.2
       local-pkg: 0.4.3
+      magic-string: 0.30.0
       pathe: 1.1.0
       picocolors: 1.0.0
       source-map: 0.6.1
@@ -19698,9 +19727,8 @@ packages:
       strip-literal: 1.0.1
       tinybench: 2.4.0
       tinypool: 0.4.0
-      tinyspy: 1.1.1
-      vite: 4.2.1(@types/node@18.15.11)(sass@1.60.0)
-      vite-node: 0.29.8(@types/node@18.15.11)(sass@1.60.0)
+      vite: 4.3.4(@types/node@18.16.3)(sass@1.62.1)
+      vite-node: 0.30.1(@types/node@18.16.3)(sass@1.62.1)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -19718,8 +19746,8 @@ packages:
   /vue-docgen-api@4.64.1(vue@3.2.47):
     resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==}
     dependencies:
-      '@babel/parser': 7.21.3
-      '@babel/types': 7.21.3
+      '@babel/parser': 7.21.4
+      '@babel/types': 7.21.4
       '@vue/compiler-dom': 3.2.47
       '@vue/compiler-sfc': 3.2.47
       ast-types: 0.14.2
@@ -19733,20 +19761,20 @@ packages:
       - vue
     dev: true
 
-  /vue-eslint-parser@9.1.1(eslint@8.37.0):
+  /vue-eslint-parser@9.1.1(eslint@8.39.0):
     resolution: {integrity: sha512-C2aI/r85Q6tYcz4dpgvrs4wH/MqVrRAVIdpYedrxnATDHHkb+TroeRcDpKWGZCx/OcECMWfz7tVwQ8e+Opy6rA==}
     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.37.0
+      eslint: 8.39.0
       eslint-scope: 7.1.1
       eslint-visitor-keys: 3.4.0
       espree: 9.5.1
       esquery: 1.4.2
       lodash: 4.17.21
-      semver: 7.3.8
+      semver: 7.5.0
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -19782,15 +19810,16 @@ packages:
       he: 1.2.0
     dev: true
 
-  /vue-tsc@1.2.0(typescript@5.0.3):
-    resolution: {integrity: sha512-rIlzqdrhyPYyLG9zxsVRa+JEseeS9s8F2BbVVVWRRsTZvJO2BbhLEb2HW3MY+DFma0378tnIqs+vfTzbcQtRFw==}
+  /vue-tsc@1.6.3(typescript@5.0.4):
+    resolution: {integrity: sha512-q7l27j0eSJgyGat0khetrvoeaAHieRZFnf8WAJyKvB3eF0AxmLqfs4ahwZhaojBJjZ/lAXZa+Xt8EX54KzQ34w==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
-      '@volar/vue-language-core': 1.2.0
-      '@volar/vue-typescript': 1.2.0
-      typescript: 5.0.3
+      '@volar/vue-language-core': 1.6.3
+      '@volar/vue-typescript': 1.6.3(typescript@5.0.4)
+      semver: 7.5.0
+      typescript: 5.0.4
     dev: true
 
   /vue@2.7.14:
@@ -19833,8 +19862,8 @@ packages:
       axios: 0.27.2(debug@4.3.4)
       joi: 17.7.0
       lodash: 4.17.21
-      minimist: 1.2.7
-      rxjs: 7.8.0
+      minimist: 1.2.8
+      rxjs: 7.8.1
     transitivePeerDependencies:
       - debug
     dev: true
@@ -19867,15 +19896,16 @@ packages:
       '@zxing/text-encoding': 0.9.0
     dev: true
 
-  /web-push@3.5.0:
-    resolution: {integrity: sha512-JC0V9hzKTqlDYJ+LTZUXtW7B175qwwaqzbbMSWDxHWxZvd3xY0C2rcotMGDavub2nAAFw+sXTsqR65/KY2A5AQ==}
+  /web-push@3.6.1:
+    resolution: {integrity: sha512-+eN2/4Ybu+nkRgXqlPXUzqeA5BgQ7J4StYJM/e2uJpsSIGhkZJFrTlCx+ow9sD0mn+4jgbo2FkFoMBKftl49ZQ==}
     engines: {node: '>= 6'}
+    hasBin: true
     dependencies:
       asn1.js: 5.4.1
       http_ece: 1.1.0
       https-proxy-agent: 5.0.1
       jws: 4.0.0
-      minimist: 1.2.7
+      minimist: 1.2.8
       urlsafe-base64: 1.0.0
     transitivePeerDependencies:
       - supports-color
@@ -19895,12 +19925,15 @@ packages:
   /webpack-sources@3.2.3:
     resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==}
     engines: {node: '>=10.13.0'}
-    dev: true
 
   /webpack-virtual-modules@0.4.6:
     resolution: {integrity: sha512-5tyDlKLqPfMqjT3Q9TAqf2YqjwmnUleZwzJi1A5qXnlBCdj2AtOJ6wAWdglTIDOPgOiOrXeBeFcsQ8+aGQ6QbA==}
     dev: true
 
+  /webpack-virtual-modules@0.5.0:
+    resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
+    dev: false
+
   /websocket@1.0.34:
     resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==}
     engines: {node: '>=4.0.0'}
@@ -19915,6 +19948,11 @@ packages:
       - supports-color
     dev: false
 
+  /well-known-symbols@2.0.0:
+    resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==}
+    engines: {node: '>=6'}
+    dev: true
+
   /whatwg-encoding@2.0.0:
     resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
     engines: {node: '>=12'}
@@ -20021,7 +20059,7 @@ packages:
     resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
     engines: {node: '>= 10.0.0'}
     dependencies:
-      '@babel/parser': 7.21.3
+      '@babel/parser': 7.21.4
       '@babel/types': 7.21.4
       assert-never: 1.2.1
       babel-walk: 3.0.0-canary-5
@@ -20126,8 +20164,8 @@ packages:
     resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==}
     engines: {node: '>=12'}
 
-  /xml2js@0.4.23:
-    resolution: {integrity: sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==}
+  /xml2js@0.5.0:
+    resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
     engines: {node: '>=4.0.0'}
     dependencies:
       sax: 1.2.4
@@ -20329,7 +20367,7 @@ packages:
       sharp: 0.31.3
     dev: false
 
-  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.2)(@storybook/components@7.0.2)(@storybook/core-events@7.0.2)(@storybook/manager-api@7.0.2)(@storybook/preview-api@7.0.2)(@storybook/theming@7.0.2)(@storybook/types@7.0.2)(react-dom@18.2.0)(react@18.2.0):
+  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.7)(@storybook/components@7.0.7)(@storybook/core-events@7.0.7)(@storybook/manager-api@7.0.7)(@storybook/preview-api@7.0.7)(@storybook/theming@7.0.7)(@storybook/types@7.0.7)(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
@@ -20350,21 +20388,21 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/components': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.2
-      '@storybook/manager-api': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.2
-      '@storybook/theming': 7.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.2
+      '@storybook/blocks': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/components': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.7
+      '@storybook/manager-api': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.7
+      '@storybook/theming': 7.0.7(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.7
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  github.com/misskey-dev/summaly/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992:
-    resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/1bab7afee616429b8bbf7a7cbcbb8ebcef66d992}
+  github.com/misskey-dev/summaly/2d63e2a0066f89871e777cc81d43c1ade8c97517:
+    resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/2d63e2a0066f89871e777cc81d43c1ade8c97517}
     name: summaly
-    version: 4.0.1
+    version: 4.0.2
     dependencies:
       cheerio: 1.0.0-rc.12
       escape-regexp: 0.0.1