From 406b4bdbe79b5b0b68fcdcb3c4b6e419460a0258 Mon Sep 17 00:00:00 2001
From: zyoshoka <107108195+zyoshoka@users.noreply.github.com>
Date: Thu, 7 Dec 2023 14:42:09 +0900
Subject: [PATCH] =?UTF-8?q?refactor(frontend):=20=E9=9D=9E=E6=8E=A8?=
 =?UTF-8?q?=E5=A5=A8=E3=81=A8=E3=81=AA=E3=81=A3=E3=81=9FReactivity=20Trans?=
 =?UTF-8?q?form=E3=82=92=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E3=82=88?=
 =?UTF-8?q?=E3=81=86=E3=81=AB=20(#12539)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* refactor(frontend): 非推奨となったReactivity Transformを使わないように

* refactor: 不要な括弧を除去

* fix: 不要なアノテーションを除去

* fix: Refの配列をrefしている部分の対応

* refactor: 不要な括弧を除去

* fix: lint

* refactor: Ref、ShallowRef、ComputedRefの変数の宣言をletからconstに置換

* fix: type error

* chore: drop reactivity transform from eslint configuration

* refactor: remove unnecessary import

* fix: 対応漏れ
---
 packages/frontend/.eslintrc.cjs               |   6 -
 packages/frontend/package.json                |   1 -
 .../frontend/src/components/MkAbuseReport.vue |   5 +-
 .../src/components/MkAchievements.vue         |  10 +-
 .../frontend/src/components/MkAnalogClock.vue |  64 +--
 packages/frontend/src/components/MkAsUi.vue   |   6 +-
 packages/frontend/src/components/MkButton.vue |  14 +-
 packages/frontend/src/components/MkChart.vue  |   4 +-
 .../frontend/src/components/MkChartLegend.vue |  19 +-
 .../frontend/src/components/MkClickerGame.vue |  14 +-
 .../frontend/src/components/MkContextMenu.vue |  16 +-
 .../src/components/MkCropperDialog.vue        |  16 +-
 packages/frontend/src/components/MkDialog.vue |  19 +-
 .../src/components/MkEmojiPickerDialog.vue    |  13 +-
 .../components/MkFileCaptionEditWindow.vue    |  10 +-
 packages/frontend/src/components/MkFolder.vue |  20 +-
 .../src/components/MkFollowButton.vue         |  24 +-
 .../src/components/MkForgotPassword.vue       |  18 +-
 .../frontend/src/components/MkHeatmap.vue     |  18 +-
 .../src/components/MkImgWithBlurhash.vue      |  45 ++-
 .../src/components/MkInstanceCardMini.vue     |   5 +-
 .../src/components/MkInstanceStats.vue        |  16 +-
 .../src/components/MkInstanceTicker.vue       |   4 +-
 .../frontend/src/components/MkLaunchPad.vue   |   6 +-
 packages/frontend/src/components/MkLink.vue   |   8 +-
 .../frontend/src/components/MkMediaBanner.vue |   4 +-
 .../frontend/src/components/MkMediaImage.vue  |  18 +-
 .../frontend/src/components/MkMediaList.vue   |   4 +-
 packages/frontend/src/components/MkMenu.vue   |  30 +-
 .../frontend/src/components/MkMiniChart.vue   |  20 +-
 packages/frontend/src/components/MkModal.vue  |  92 ++---
 .../frontend/src/components/MkModalWindow.vue |  24 +-
 packages/frontend/src/components/MkNote.vue   |  78 ++--
 .../src/components/MkNoteDetailed.vue         |  76 ++--
 .../frontend/src/components/MkNoteSimple.vue  |   4 +-
 .../frontend/src/components/MkNoteSub.vue     |   6 +-
 .../components/MkNotificationSelectWindow.vue |   6 +-
 .../src/components/MkNotifications.vue        |   2 +-
 packages/frontend/src/components/MkOmit.vue   |  14 +-
 .../frontend/src/components/MkPageWindow.vue  |  38 +-
 .../frontend/src/components/MkPagination.vue  |  60 +--
 .../src/components/MkPasswordDialog.vue       |  18 +-
 .../src/components/MkPlusOneEffect.vue        |   6 +-
 .../frontend/src/components/MkPopupMenu.vue   |   8 +-
 .../frontend/src/components/MkPostForm.vue    | 368 +++++++++---------
 .../src/components/MkPostFormDialog.vue       |   8 +-
 .../src/components/MkPullToRefresh.vue        |  78 ++--
 .../MkPushNotificationAllowButton.vue         |  41 +-
 packages/frontend/src/components/MkRadio.vue  |   4 +-
 .../src/components/MkReactionEffect.vue       |   6 +-
 .../src/components/MkReactionsViewer.vue      |  26 +-
 .../src/components/MkRetentionHeatmap.vue     |  16 +-
 packages/frontend/src/components/MkSignin.vue |  90 ++---
 .../src/components/MkSigninDialog.vue         |   8 +-
 .../src/components/MkSignupDialog.form.vue    | 148 +++----
 .../src/components/MkSignupDialog.vue         |  12 +-
 .../src/components/MkSubNoteContent.vue       |   4 +-
 .../frontend/src/components/MkTagCloud.vue    |  20 +-
 .../frontend/src/components/MkTimeline.vue    |  10 +-
 packages/frontend/src/components/MkToast.vue  |   6 +-
 .../src/components/MkTokenGenerateWindow.vue  |  26 +-
 .../frontend/src/components/MkUrlPreview.vue  |  60 +--
 .../src/components/MkUrlPreviewPopup.vue      |  10 +-
 .../MkUserAnnouncementEditDialog.vue          |  32 +-
 .../src/components/MkUserCardMini.vue         |   6 +-
 .../src/components/MkUserOnlineIndicator.vue  |   4 +-
 .../frontend/src/components/MkUserPopup.vue   |  18 +-
 .../src/components/MkUserSelectDialog.vue     |  40 +-
 .../components/MkUserSetupDialog.Privacy.vue  |   8 +-
 .../src/components/MkVisibilityPicker.vue     |  10 +-
 .../MkVisitorDashboard.ActiveUsersChart.vue   |  10 +-
 .../src/components/MkVisitorDashboard.vue     |  10 +-
 packages/frontend/src/components/MkWindow.vue |  94 ++---
 .../src/components/MkYouTubePlayer.vue        |  15 +-
 .../frontend/src/components/global/MkA.vue    |   3 +-
 .../frontend/src/components/global/MkAd.vue   |   2 +-
 .../src/components/global/MkAvatar.vue        |  16 +-
 .../src/components/global/MkCustomEmoji.vue   |   4 +-
 .../src/components/global/MkPageHeader.vue    |  28 +-
 .../components/global/MkStickyContainer.vue   |  62 +--
 .../frontend/src/components/global/MkTime.vue |  42 +-
 .../src/components/global/RouterView.vue      |  14 +-
 packages/frontend/src/pages/_error_.vue       |  22 +-
 packages/frontend/src/pages/about-misskey.vue |  26 +-
 packages/frontend/src/pages/about.emojis.vue  |  32 +-
 .../frontend/src/pages/about.federation.vue   |  26 +-
 packages/frontend/src/pages/about.vue         |  16 +-
 packages/frontend/src/pages/admin-file.vue    |  30 +-
 packages/frontend/src/pages/admin-user.vue    |  94 ++---
 .../frontend/src/pages/admin/_header_.vue     |   8 +-
 packages/frontend/src/pages/admin/abuses.vue  |  26 +-
 packages/frontend/src/pages/admin/ads.vue     |  20 +-
 .../src/pages/admin/announcements.vue         |  20 +-
 .../src/pages/admin/bot-protection.vue        |  48 +--
 .../frontend/src/pages/admin/branding.vue     |  76 ++--
 .../frontend/src/pages/admin/database.vue     |   6 +-
 .../src/pages/admin/email-settings.vue        |  46 +--
 .../src/pages/admin/external-services.vue     |  18 +-
 .../frontend/src/pages/admin/federation.vue   |  30 +-
 packages/frontend/src/pages/admin/files.vue   |  24 +-
 packages/frontend/src/pages/admin/index.vue   | 100 ++---
 .../src/pages/admin/instance-block.vue        |  19 +-
 packages/frontend/src/pages/admin/invites.vue |   8 +-
 .../frontend/src/pages/admin/moderation.vue   |  46 +--
 packages/frontend/src/pages/admin/modlog.vue  |  16 +-
 .../src/pages/admin/object-storage.vue        |  82 ++--
 .../src/pages/admin/other-settings.vue        |  30 +-
 .../src/pages/admin/overview.active-users.vue |  10 +-
 .../src/pages/admin/overview.ap-requests.vue  |  14 +-
 .../src/pages/admin/overview.federation.vue   |  30 +-
 .../src/pages/admin/overview.heatmap.vue      |   3 +-
 .../src/pages/admin/overview.moderators.vue   |  10 +-
 .../src/pages/admin/overview.queue.vue        |  26 +-
 .../src/pages/admin/overview.stats.vue        |  22 +-
 .../src/pages/admin/overview.users.vue        |   9 +-
 .../frontend/src/pages/admin/overview.vue     |  44 +--
 .../src/pages/admin/proxy-account.vue         |  22 +-
 .../frontend/src/pages/admin/queue.chart.vue  |  26 +-
 packages/frontend/src/pages/admin/queue.vue   |   9 +-
 packages/frontend/src/pages/admin/relays.vue  |  10 +-
 .../frontend/src/pages/admin/roles.edit.vue   |  28 +-
 .../frontend/src/pages/admin/roles.editor.vue |  54 +--
 .../frontend/src/pages/admin/roles.role.vue   |  14 +-
 packages/frontend/src/pages/admin/roles.vue   |   4 +-
 .../frontend/src/pages/admin/security.vue     |  80 ++--
 .../frontend/src/pages/admin/server-rules.vue |  10 +-
 .../frontend/src/pages/admin/settings.vue     | 118 +++---
 packages/frontend/src/pages/admin/users.vue   |  30 +-
 packages/frontend/src/pages/announcements.vue |   6 +-
 .../frontend/src/pages/antenna-timeline.vue   |  30 +-
 packages/frontend/src/pages/api-console.vue   |   6 +-
 packages/frontend/src/pages/auth.form.vue     |   8 +-
 packages/frontend/src/pages/auth.vue          |  28 +-
 .../frontend/src/pages/avatar-decorations.vue |  14 +-
 .../frontend/src/pages/channel-editor.vue     |  66 ++--
 packages/frontend/src/pages/channel.vue       |  64 +--
 packages/frontend/src/pages/channels.vue      |  30 +-
 packages/frontend/src/pages/clip.vue          |  50 +--
 .../src/pages/custom-emojis-manager.vue       |   4 +-
 packages/frontend/src/pages/drive.vue         |  10 +-
 .../frontend/src/pages/emoji-edit-dialog.vue  |  66 ++--
 .../frontend/src/pages/explore.featured.vue   |   3 +-
 packages/frontend/src/pages/explore.roles.vue |   6 +-
 packages/frontend/src/pages/explore.users.vue |  18 +-
 packages/frontend/src/pages/explore.vue       |  12 +-
 .../frontend/src/pages/flash/flash-edit.vue   |  58 +--
 .../frontend/src/pages/flash/flash-index.vue  |   8 +-
 packages/frontend/src/pages/flash/flash.vue   |  78 ++--
 .../frontend/src/pages/follow-requests.vue    |   4 +-
 packages/frontend/src/pages/gallery/edit.vue  |  46 +--
 packages/frontend/src/pages/gallery/index.vue |  16 +-
 packages/frontend/src/pages/gallery/post.vue  |  44 +--
 packages/frontend/src/pages/instance-info.vue |  62 +--
 packages/frontend/src/pages/list.vue          |  38 +-
 packages/frontend/src/pages/miauth.vue        |  14 +-
 .../frontend/src/pages/my-antennas/create.vue |   3 +-
 .../frontend/src/pages/my-antennas/edit.vue   |   5 +-
 .../frontend/src/pages/my-antennas/editor.vue |  60 +--
 .../frontend/src/pages/my-antennas/index.vue  |   8 +-
 .../frontend/src/pages/my-clips/index.vue     |  22 +-
 .../frontend/src/pages/my-lists/index.vue     |   8 +-
 packages/frontend/src/pages/my-lists/list.vue |  40 +-
 packages/frontend/src/pages/not-found.vue     |   5 +-
 packages/frontend/src/pages/note.vue          |  56 +--
 packages/frontend/src/pages/notifications.vue |  24 +-
 .../page-editor/els/page-editor.el.image.vue  |  10 +-
 .../page-editor/els/page-editor.el.note.vue   |  16 +-
 .../els/page-editor.el.section.vue            |  10 +-
 .../page-editor/els/page-editor.el.text.vue   |   8 +-
 .../src/pages/page-editor/page-editor.vue     | 128 +++---
 packages/frontend/src/pages/page.vue          |  60 +--
 packages/frontend/src/pages/pages.vue         |   8 +-
 packages/frontend/src/pages/registry.keys.vue |  16 +-
 .../frontend/src/pages/registry.value.vue     |  34 +-
 packages/frontend/src/pages/registry.vue      |   9 +-
 .../frontend/src/pages/reset-password.vue     |  10 +-
 packages/frontend/src/pages/role.vue          |  28 +-
 packages/frontend/src/pages/scratchpad.vue    |  16 +-
 packages/frontend/src/pages/search.note.vue   |  28 +-
 packages/frontend/src/pages/search.user.vue   |  18 +-
 packages/frontend/src/pages/search.vue        |   8 +-
 packages/frontend/src/pages/settings/2fa.vue  |   4 +-
 .../frontend/src/pages/settings/accounts.vue  |   6 +-
 packages/frontend/src/pages/settings/api.vue  |   6 +-
 packages/frontend/src/pages/settings/apps.vue |   6 +-
 .../src/pages/settings/custom-css.vue         |   6 +-
 packages/frontend/src/pages/settings/deck.vue |   4 +-
 .../src/pages/settings/drive-cleaner.vue      |   2 +-
 .../frontend/src/pages/settings/drive.vue     |  14 +-
 .../frontend/src/pages/settings/email.vue     |   6 +-
 .../frontend/src/pages/settings/general.vue   |   4 +-
 .../src/pages/settings/import-export.vue      |   6 +-
 .../frontend/src/pages/settings/index.vue     |  64 +--
 .../src/pages/settings/mute-block.vue         |  30 +-
 .../frontend/src/pages/settings/navbar.vue    |   4 +-
 .../notifications.notification-config.vue     |   8 +-
 .../src/pages/settings/notifications.vue      |  20 +-
 .../frontend/src/pages/settings/other.vue     |   4 +-
 .../src/pages/settings/plugin.install.vue     |   6 +-
 .../frontend/src/pages/settings/plugin.vue    |   6 +-
 .../frontend/src/pages/settings/privacy.vue   |  46 +--
 .../frontend/src/pages/settings/profile.vue   |   8 +-
 .../frontend/src/pages/settings/reaction.vue  |  28 +-
 .../frontend/src/pages/settings/roles.vue     |   4 +-
 .../frontend/src/pages/settings/security.vue  |   5 +-
 .../frontend/src/pages/settings/sounds.vue    |   4 +-
 .../frontend/src/pages/settings/statusbar.vue |  10 +-
 .../src/pages/settings/theme.install.vue      |   8 +-
 .../src/pages/settings/theme.manage.vue       |   4 +-
 .../frontend/src/pages/settings/theme.vue     |   4 +-
 .../src/pages/settings/webhook.edit.vue       |  50 +--
 .../src/pages/settings/webhook.new.vue        |  46 +--
 .../frontend/src/pages/settings/webhook.vue   |   6 +-
 packages/frontend/src/pages/share.vue         |  44 +--
 .../frontend/src/pages/signup-complete.vue    |  10 +-
 packages/frontend/src/pages/tag.vue           |   4 +-
 packages/frontend/src/pages/theme-editor.vue  |  64 +--
 packages/frontend/src/pages/timeline.vue      |  58 +--
 .../frontend/src/pages/user-list-timeline.vue |  24 +-
 packages/frontend/src/pages/user-tag.vue      |   2 +-
 .../src/pages/user/activity.following.vue     |  14 +-
 .../src/pages/user/activity.heatmap.vue       |  18 +-
 .../src/pages/user/activity.notes.vue         |  14 +-
 .../frontend/src/pages/user/activity.pv.vue   |  14 +-
 .../frontend/src/pages/user/followers.vue     |  24 +-
 .../frontend/src/pages/user/following.vue     |  24 +-
 packages/frontend/src/pages/user/home.vue     |  60 +--
 .../src/pages/user/index.activity.vue         |  12 +-
 .../frontend/src/pages/user/index.files.vue   |  12 +-
 packages/frontend/src/pages/user/index.vue    |  36 +-
 .../frontend/src/pages/welcome.entrance.a.vue |  10 +-
 packages/frontend/src/pages/welcome.setup.vue |  18 +-
 .../frontend/src/pages/welcome.timeline.vue   |  18 +-
 packages/frontend/src/pages/welcome.vue       |  10 +-
 packages/frontend/src/ui/_common_/common.vue  |   8 +-
 .../src/ui/_common_/statusbar-federation.vue  |   4 +-
 .../src/ui/_common_/statusbar-rss.vue         |   4 +-
 .../src/ui/_common_/statusbar-user-list.vue   |   4 +-
 .../src/ui/_common_/stream-indicator.vue      |   8 +-
 packages/frontend/src/ui/classic.header.vue   |  12 +-
 packages/frontend/src/ui/classic.sidebar.vue  |  16 +-
 packages/frontend/src/ui/classic.vue          |  42 +-
 packages/frontend/src/ui/deck.vue             |   6 +-
 .../frontend/src/ui/deck/antenna-column.vue   |   4 +-
 .../frontend/src/ui/deck/channel-column.vue   |  11 +-
 packages/frontend/src/ui/deck/column.vue      |  36 +-
 .../frontend/src/ui/deck/direct-column.vue    |   6 +-
 packages/frontend/src/ui/deck/list-column.vue |  10 +-
 packages/frontend/src/ui/deck/main-column.vue |   6 +-
 .../frontend/src/ui/deck/mentions-column.vue  |   6 +-
 .../src/ui/deck/notifications-column.vue      |   4 +-
 .../src/ui/deck/role-timeline-column.vue      |   4 +-
 packages/frontend/src/ui/deck/tl-column.vue   |  30 +-
 .../frontend/src/ui/deck/widgets-column.vue   |   6 +-
 packages/frontend/src/ui/minimum.vue          |  10 +-
 packages/frontend/src/ui/universal.vue        |  26 +-
 .../frontend/src/ui/universal.widgets.vue     |   6 +-
 packages/frontend/src/ui/visitor.vue          |  32 +-
 packages/frontend/src/ui/zen.vue              |  10 +-
 .../src/widgets/WidgetActivity.chart.vue      |  35 +-
 .../src/widgets/WidgetAiscriptApp.vue         |   4 +-
 packages/frontend/src/widgets/WidgetClock.vue |   8 +-
 .../src/widgets/WidgetDigitalClock.vue        |   7 +-
 .../src/widgets/WidgetInstanceCloud.vue       |  10 +-
 .../frontend/src/widgets/WidgetJobQueue.vue   |  16 +-
 packages/frontend/src/widgets/WidgetRss.vue   |   8 +-
 .../frontend/src/widgets/WidgetRssTicker.vue  |  12 +-
 .../frontend/src/widgets/WidgetUserList.vue   |  17 +-
 .../src/widgets/server-metric/cpu-mem.vue     |  56 +--
 .../src/widgets/server-metric/cpu.vue         |   6 +-
 .../src/widgets/server-metric/disk.vue        |  10 +-
 .../src/widgets/server-metric/mem.vue         |  18 +-
 .../src/widgets/server-metric/net.vue         |  60 +--
 .../src/widgets/server-metric/pie.vue         |   6 +-
 packages/frontend/tsconfig.json               |   1 -
 packages/frontend/vite.config.ts              |   7 +-
 pnpm-lock.yaml                                | 166 ++------
 277 files changed, 3353 insertions(+), 3441 deletions(-)

diff --git a/packages/frontend/.eslintrc.cjs b/packages/frontend/.eslintrc.cjs
index 77038f0dfa..20f88dc078 100644
--- a/packages/frontend/.eslintrc.cjs
+++ b/packages/frontend/.eslintrc.cjs
@@ -69,12 +69,6 @@ module.exports = {
 		'require': false,
 		'__dirname': false,
 
-		// Vue
-		'$$': false,
-		'$ref': false,
-		'$shallowRef': false,
-		'$computed': false,
-
 		// Misskey
 		'_DEV_': false,
 		'_LANGS_': false,
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 4f3b74649a..9ecb47afba 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -25,7 +25,6 @@
 		"@syuilo/aiscript": "0.16.0",
 		"@tabler/icons-webfont": "2.37.0",
 		"@vitejs/plugin-vue": "4.5.1",
-		"@vue-macros/reactivity-transform": "0.4.0",
 		"@vue/compiler-sfc": "3.3.9",
 		"astring": "1.8.6",
 		"autosize": "6.0.1",
diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index 2c7be319cb..ce7e134b70 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -41,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
@@ -56,11 +57,11 @@ const emit = defineEmits<{
 	(ev: 'resolved', reportId: string): void;
 }>();
 
-let forward = $ref(props.report.forwarded);
+const forward = ref(props.report.forwarded);
 
 function resolve() {
 	os.apiWithDialog('admin/resolve-abuse-user-report', {
-		forward: forward,
+		forward: forward.value,
 		reportId: props.report.id,
 	}).then(() => {
 		emit('resolved', props.report.id);
diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue
index bea0ed26d8..b067e94a6d 100644
--- a/packages/frontend/src/components/MkAchievements.vue
+++ b/packages/frontend/src/components/MkAchievements.vue
@@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { onMounted } from 'vue';
+import { onMounted, ref, computed } from 'vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js';
@@ -67,15 +67,15 @@ const props = withDefaults(defineProps<{
 	withDescription: true,
 });
 
-let achievements = $ref();
-const lockedAchievements = $computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements ?? []).some(a => a.name === x)));
+const achievements = ref();
+const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
 
 function fetch() {
 	os.api('users/achievements', { userId: props.user.id }).then(res => {
-		achievements = [];
+		achievements.value = [];
 		for (const t of ACHIEVEMENT_TYPES) {
 			const a = res.find(x => x.name === t);
-			if (a) achievements.push(a);
+			if (a) achievements.value.push(a);
 		}
 		//achievements = res.sort((a, b) => b.unlockedAt - a.unlockedAt);
 	});
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index cd2c4d8264..0e252f7b1d 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -138,45 +138,45 @@ const texts = computed(() => {
 });
 
 let enabled = true;
-let majorGraduationColor = $ref<string>();
+const majorGraduationColor = ref<string>();
 //let minorGraduationColor = $ref<string>();
-let sHandColor = $ref<string>();
-let mHandColor = $ref<string>();
-let hHandColor = $ref<string>();
-let nowColor = $ref<string>();
-let h = $ref<number>(0);
-let m = $ref<number>(0);
-let s = $ref<number>(0);
-let hAngle = $ref<number>(0);
-let mAngle = $ref<number>(0);
-let sAngle = $ref<number>(0);
-let disableSAnimate = $ref(false);
+const sHandColor = ref<string>();
+const mHandColor = ref<string>();
+const hHandColor = ref<string>();
+const nowColor = ref<string>();
+const h = ref<number>(0);
+const m = ref<number>(0);
+const s = ref<number>(0);
+const hAngle = ref<number>(0);
+const mAngle = ref<number>(0);
+const sAngle = ref<number>(0);
+const disableSAnimate = ref(false);
 let sOneRound = false;
 const sLine = ref<SVGPathElement>();
 
 function tick() {
 	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) {
+	const previousS = s.value;
+	const previousM = m.value;
+	const previousH = h.value;
+	s.value = now.getSeconds();
+	m.value = now.getMinutes();
+	h.value = now.getHours();
+	if (previousS === s.value && previousM === m.value && previousH === h.value) {
 		return;
 	}
-	hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
-	mAngle = Math.PI * (m + s / 60) / 30;
+	hAngle.value = Math.PI * (h.value % (props.twentyfour ? 24 : 12) + (m.value + s.value / 60) / 60) / (props.twentyfour ? 12 : 6);
+	mAngle.value = Math.PI * (m.value + s.value / 60) / 30;
 	if (sOneRound && sLine.value) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
-		sAngle = Math.PI * 60 / 30;
+		sAngle.value = Math.PI * 60 / 30;
 		defaultIdlingRenderScheduler.delete(tick);
 		sLine.value.addEventListener('transitionend', () => {
-			disableSAnimate = true;
+			disableSAnimate.value = true;
 			requestAnimationFrame(() => {
-				sAngle = 0;
+				sAngle.value = 0;
 				requestAnimationFrame(() => {
-					disableSAnimate = false;
+					disableSAnimate.value = false;
 					if (enabled) {
 						defaultIdlingRenderScheduler.add(tick);
 					}
@@ -184,9 +184,9 @@ function tick() {
 			});
 		}, { once: true });
 	} else {
-		sAngle = Math.PI * s / 30;
+		sAngle.value = Math.PI * s.value / 30;
 	}
-	sOneRound = s === 59;
+	sOneRound = s.value === 59;
 }
 
 tick();
@@ -195,12 +195,12 @@ function calcColors() {
 	const computedStyle = getComputedStyle(document.documentElement);
 	const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark();
 	const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString();
-	majorGraduationColor = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
+	majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';
 	//minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
-	sHandColor = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
-	mHandColor = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
-	hHandColor = accent;
-	nowColor = accent;
+	sHandColor.value = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)';
+	mHandColor.value = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString();
+	hHandColor.value = accent;
+	nowColor.value = accent;
 }
 
 calcColors();
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 099baf0d72..7670b54f16 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { Ref } from 'vue';
+import { Ref, ref } from 'vue';
 import * as os from '@/os.js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -87,10 +87,10 @@ function g(id) {
 	return props.components.find(x => x.value.id === id).value;
 }
 
-let valueForSwitch = $ref(c.default ?? false);
+const valueForSwitch = ref(c.default ?? false);
 
 function onSwitchUpdate(v) {
-	valueForSwitch = v;
+	valueForSwitch.value = v;
 	if (c.onChange) c.onChange(v);
 }
 
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index bcd58ae516..8b176eedaa 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted } from 'vue';
+import { nextTick, onMounted, shallowRef } from 'vue';
 
 const props = defineProps<{
 	type?: 'button' | 'submit' | 'reset';
@@ -59,13 +59,13 @@ const emit = defineEmits<{
 	(ev: 'click', payload: MouseEvent): void;
 }>();
 
-let el = $shallowRef<HTMLElement | null>(null);
-let ripples = $shallowRef<HTMLElement | null>(null);
+const el = shallowRef<HTMLElement | null>(null);
+const ripples = shallowRef<HTMLElement | null>(null);
 
 onMounted(() => {
 	if (props.autofocus) {
 		nextTick(() => {
-			el!.focus();
+			el.value!.focus();
 		});
 	}
 });
@@ -88,11 +88,11 @@ function onMousedown(evt: MouseEvent): void {
 	const rect = target.getBoundingClientRect();
 
 	const ripple = document.createElement('div');
-	ripple.classList.add(ripples!.dataset.childrenClass!);
+	ripple.classList.add(ripples.value!.dataset.childrenClass!);
 	ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
 	ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
 
-	ripples!.appendChild(ripple);
+	ripples.value!.appendChild(ripple);
 
 	const circleCenterX = evt.clientX - rect.left;
 	const circleCenterY = evt.clientY - rect.top;
@@ -107,7 +107,7 @@ function onMousedown(evt: MouseEvent): void {
 		ripple.style.opacity = '0';
 	}, 1000);
 	window.setTimeout(() => {
-		if (ripples) ripples.removeChild(ripple);
+		if (ripples.value) ripples.value.removeChild(ripple);
 	}, 2000);
 }
 </script>
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index fe7077bdbf..adb3c134ae 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -74,7 +74,7 @@ const props = defineProps({
 	},
 });
 
-let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
 
 const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
 const negate = arr => arr.map(x => -x);
@@ -268,7 +268,7 @@ const render = () => {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl)] : [])],
+		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl.value)] : [])],
 	});
 };
 
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
index d321114cba..1a1b4323d9 100644
--- a/packages/frontend/src/components/MkChartLegend.vue
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -13,29 +13,30 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { shallowRef } from 'vue';
 import { Chart, LegendItem } from 'chart.js';
 
 const props = defineProps({
 });
 
-let chart = $shallowRef<Chart>();
-let items = $shallowRef<LegendItem[]>([]);
+const chart = shallowRef<Chart>();
+const items = shallowRef<LegendItem[]>([]);
 
 function update(_chart: Chart, _items: LegendItem[]) {
-	chart = _chart,
-	items = _items;
+	chart.value = _chart,
+	items.value = _items;
 }
 
 function onClick(item: LegendItem) {
-	if (chart == null) return;
-	const { type } = chart.config;
+	if (chart.value == null) return;
+	const { type } = chart.value.config;
 	if (type === 'pie' || type === 'doughnut') {
 		// Pie and doughnut charts only have a single dataset and visibility is per item
-		chart.toggleDataVisibility(item.index);
+		chart.value.toggleDataVisibility(item.index);
 	} else {
-		chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
+		chart.value.setDatasetVisibility(item.datasetIndex, !chart.value.isDatasetVisible(item.datasetIndex));
 	}
-	chart.update();
+	chart.value.update();
 }
 
 defineExpose({
diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index 1c3920962e..f255961e25 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted } from 'vue';
+import { computed, onMounted, onUnmounted, ref } from 'vue';
 import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
 import * as os from '@/os.js';
 import { useInterval } from '@/scripts/use-interval.js';
@@ -29,8 +29,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
 
 const saveData = game.saveData;
 const cookies = computed(() => saveData.value?.cookies);
-let cps = $ref(0);
-let prevCookies = $ref(0);
+const cps = ref(0);
+const prevCookies = ref(0);
 
 function onClick(ev: MouseEvent) {
 	const x = ev.clientX;
@@ -48,9 +48,9 @@ function onClick(ev: MouseEvent) {
 }
 
 useInterval(() => {
-	const diff = saveData.value!.cookies - prevCookies;
-	cps = diff;
-	prevCookies = saveData.value!.cookies;
+	const diff = saveData.value!.cookies - prevCookies.value;
+	cps.value = diff;
+	prevCookies.value = saveData.value!.cookies;
 }, 1000, {
 	immediate: false,
 	afterMounted: true,
@@ -63,7 +63,7 @@ useInterval(game.save, 1000 * 5, {
 
 onMounted(async () => {
 	await game.load();
-	prevCookies = saveData.value!.cookies;
+	prevCookies.value = saveData.value!.cookies;
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 6cca7fc353..b78252be89 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
+import { onMounted, onBeforeUnmount, shallowRef, ref } from 'vue';
 import MkMenu from './MkMenu.vue';
 import { MenuItem } from './types/menu.vue';
 import contains from '@/scripts/contains.js';
@@ -34,9 +34,9 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let rootEl = $shallowRef<HTMLDivElement>();
+const rootEl = shallowRef<HTMLDivElement>();
 
-let zIndex = $ref<number>(os.claimZIndex('high'));
+const zIndex = ref<number>(os.claimZIndex('high'));
 
 const SCROLLBAR_THICKNESS = 16;
 
@@ -44,8 +44,8 @@ onMounted(() => {
 	let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
 	let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
 
-	const width = rootEl.offsetWidth;
-	const height = rootEl.offsetHeight;
+	const width = rootEl.value.offsetWidth;
+	const height = rootEl.value.offsetHeight;
 
 	if (left + width - window.pageXOffset >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
 		left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset;
@@ -63,8 +63,8 @@ onMounted(() => {
 		left = 0;
 	}
 
-	rootEl.style.top = `${top}px`;
-	rootEl.style.left = `${left}px`;
+	rootEl.value.style.top = `${top}px`;
+	rootEl.value.style.left = `${left}px`;
 
 	document.body.addEventListener('mousedown', onMousedown);
 });
@@ -74,7 +74,7 @@ onBeforeUnmount(() => {
 });
 
 function onMousedown(evt: Event) {
-	if (!contains(rootEl, evt.target) && (rootEl !== evt.target)) emit('closed');
+	if (!contains(rootEl.value, evt.target) && (rootEl.value !== evt.target)) emit('closed');
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 81f3936600..0a1ddd3171 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import Cropper from 'cropperjs';
 import tinycolor from 'tinycolor2';
@@ -56,10 +56,10 @@ const props = defineProps<{
 }>();
 
 const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
-let dialogEl = $shallowRef<InstanceType<typeof MkModalWindow>>();
-let imgEl = $shallowRef<HTMLImageElement>();
+const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const imgEl = shallowRef<HTMLImageElement>();
 let cropper: Cropper | null = null;
-let loading = $ref(true);
+const loading = ref(true);
 
 const ok = async () => {
 	const promise = new Promise<Misskey.entities.DriveFile>(async (res) => {
@@ -94,16 +94,16 @@ const ok = async () => {
 	const f = await promise;
 
 	emit('ok', f);
-	dialogEl!.close();
+	dialogEl.value!.close();
 };
 
 const cancel = () => {
 	emit('cancel');
-	dialogEl!.close();
+	dialogEl.value!.close();
 };
 
 const onImageLoad = () => {
-	loading = false;
+	loading.value = false;
 
 	if (cropper) {
 		cropper.getCropperImage()!.$center('contain');
@@ -112,7 +112,7 @@ const onImageLoad = () => {
 };
 
 onMounted(() => {
-	cropper = new Cropper(imgEl!, {
+	cropper = new Cropper(imgEl.value!, {
 	});
 
 	const computedStyle = getComputedStyle(document.documentElement);
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 33757ccc83..3c1f83d335 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -30,8 +30,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
 			<template v-if="input.type === 'password'" #prefix><i class="ti ti-lock"></i></template>
 			<template #caption>
-				<span v-if="okButtonDisabled && disabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
-				<span v-else-if="okButtonDisabled && disabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
+				<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
+				<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
 			</template>
 		</MkInput>
 		<MkSelect v-if="select" v-model="selectedValue" autofocus>
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</template>
 		</MkSelect>
 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
-			<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="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @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">
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
+import { onBeforeUnmount, onMounted, ref, shallowRef, computed } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -122,24 +122,21 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
 const inputValue = ref<string | number | null>(props.input?.default ?? null);
 const selectedValue = ref(props.select?.default ?? null);
 
-let disabledReason = $ref<null | 'charactersExceeded' | 'charactersBelow'>(null);
-const okButtonDisabled = $computed<boolean>(() => {
+const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'charactersBelow'>(() => {
 	if (props.input) {
 		if (props.input.minLength) {
 			if ((inputValue.value || inputValue.value === '') && (inputValue.value as string).length < props.input.minLength) {
-				disabledReason = 'charactersBelow';
-				return true;
+				return 'charactersBelow';
 			}
 		}
 		if (props.input.maxLength) {
 			if (inputValue.value && (inputValue.value as string).length > props.input.maxLength) {
-				disabledReason = 'charactersExceeded';
-				return true;
+				return 'charactersExceeded';
 			}
 		}
 	}
 
-	return false;
+	return null;
 });
 
 function done(canceled: boolean, result?) {
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 05b137e335..2cce1f5520 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -31,6 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 import { defaultStore } from '@/store.js';
@@ -54,23 +55,23 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = $shallowRef<InstanceType<typeof MkModal>>();
-const picker = $shallowRef<InstanceType<typeof MkEmojiPicker>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
+const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
 
 function chosen(emoji: any) {
 	emit('done', emoji);
 	if (props.choseAndClose) {
-		modal?.close();
+		modal.value?.close();
 	}
 }
 
 function opening() {
-	picker?.reset();
-	picker?.focus();
+	picker.value?.reset();
+	picker.value?.focus();
 
 	// 何故かちょっと待たないとフォーカスされない
 	setTimeout(() => {
-		picker?.focus();
+		picker.value?.focus();
 	}, 10);
 }
 </script>
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index 28888fb9c8..922089a78b 100644
--- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue
+++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { shallowRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -42,12 +42,12 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
-let caption = $ref(props.default);
+const caption = ref(props.default);
 
 async function ok() {
-	emit('done', caption);
-	dialog.close();
+	emit('done', caption.value);
+	dialog.value.close();
 }
 </script>
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 60ecc13056..6b7dfb20e3 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted } from 'vue';
+import { nextTick, onMounted, shallowRef, ref } from 'vue';
 import { defaultStore } from '@/store.js';
 
 const props = withDefaults(defineProps<{
@@ -70,10 +70,10 @@ const getBgColor = (el: HTMLElement) => {
 	}
 };
 
-let rootEl = $shallowRef<HTMLElement>();
-let bgSame = $ref(false);
-let opened = $ref(props.defaultOpen);
-let openedAtLeastOnce = $ref(props.defaultOpen);
+const rootEl = shallowRef<HTMLElement>();
+const bgSame = ref(false);
+const opened = ref(props.defaultOpen);
+const openedAtLeastOnce = ref(props.defaultOpen);
 
 function enter(el) {
 	const elementHeight = el.getBoundingClientRect().height;
@@ -98,20 +98,20 @@ function afterLeave(el) {
 }
 
 function toggle() {
-	if (!opened) {
-		openedAtLeastOnce = true;
+	if (!opened.value) {
+		openedAtLeastOnce.value = true;
 	}
 
 	nextTick(() => {
-		opened = !opened;
+		opened.value = !opened.value;
 	});
 }
 
 onMounted(() => {
 	const computedStyle = getComputedStyle(document.documentElement);
-	const parentBg = getBgColor(rootEl.parentElement);
+	const parentBg = getBgColor(rootEl.value.parentElement);
 	const myBg = computedStyle.getPropertyValue('--panel');
-	bgSame = parentBg === myBg;
+	bgSame.value = parentBg === myBg;
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index b8de71e3b7..88d3188189 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onBeforeUnmount, onMounted } from 'vue';
+import { onBeforeUnmount, onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
 import { useStream } from '@/stream.js';
@@ -57,9 +57,9 @@ const emit = defineEmits<{
 	(_: 'update:user', value: Misskey.entities.UserDetailed): void
 }>();
 
-let isFollowing = $ref(props.user.isFollowing);
-let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
-let wait = $ref(false);
+const isFollowing = ref(props.user.isFollowing);
+const hasPendingFollowRequestFromYou = ref(props.user.hasPendingFollowRequestFromYou);
+const wait = ref(false);
 const connection = useStream().useChannel('main');
 
 if (props.user.isFollowing == null) {
@@ -71,16 +71,16 @@ if (props.user.isFollowing == null) {
 
 function onFollowChange(user: Misskey.entities.UserDetailed) {
 	if (user.id === props.user.id) {
-		isFollowing = user.isFollowing;
-		hasPendingFollowRequestFromYou = user.hasPendingFollowRequestFromYou;
+		isFollowing.value = user.isFollowing;
+		hasPendingFollowRequestFromYou.value = user.hasPendingFollowRequestFromYou;
 	}
 }
 
 async function onClick() {
-	wait = true;
+	wait.value = true;
 
 	try {
-		if (isFollowing) {
+		if (isFollowing.value) {
 			const { canceled } = await os.confirm({
 				type: 'warning',
 				text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }),
@@ -92,11 +92,11 @@ async function onClick() {
 				userId: props.user.id,
 			});
 		} else {
-			if (hasPendingFollowRequestFromYou) {
+			if (hasPendingFollowRequestFromYou.value) {
 				await os.api('following/requests/cancel', {
 					userId: props.user.id,
 				});
-				hasPendingFollowRequestFromYou = false;
+				hasPendingFollowRequestFromYou.value = false;
 			} else {
 				await os.api('following/create', {
 					userId: props.user.id,
@@ -106,7 +106,7 @@ async function onClick() {
 					...props.user,
 					withReplies: defaultStore.state.defaultWithReplies
 				});
-				hasPendingFollowRequestFromYou = true;
+				hasPendingFollowRequestFromYou.value = true;
 
 				claimAchievement('following1');
 
@@ -127,7 +127,7 @@ async function onClick() {
 	} catch (err) {
 		console.error(err);
 	} finally {
-		wait = false;
+		wait.value = false;
 	}
 }
 
diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue
index 521ac11d12..9b57688a02 100644
--- a/packages/frontend/src/components/MkForgotPassword.vue
+++ b/packages/frontend/src/components/MkForgotPassword.vue
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -53,19 +53,19 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let dialog: InstanceType<typeof MkModalWindow> = $ref();
+const dialog = ref<InstanceType<typeof MkModalWindow>>();
 
-let username = $ref('');
-let email = $ref('');
-let processing = $ref(false);
+const username = ref('');
+const email = ref('');
+const processing = ref(false);
 
 async function onSubmit() {
-	processing = true;
+	processing.value = true;
 	await os.apiWithDialog('request-reset-password', {
-		username,
-		email,
+		username: username.value,
+		email: email.value,
 	});
 	emit('done');
-	dialog.close();
+	dialog.value.close();
 }
 </script>
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index 0022531e58..a57e6c9292 100644
--- a/packages/frontend/src/components/MkHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, nextTick, watch } from 'vue';
+import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -27,11 +27,11 @@ const props = defineProps<{
 	src: string;
 }>();
 
-const rootEl = $shallowRef<HTMLDivElement>(null);
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
+const rootEl = shallowRef<HTMLDivElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip({
 	position: 'middle',
@@ -42,8 +42,8 @@ async function renderChart() {
 		chartInstance.destroy();
 	}
 
-	const wide = rootEl.offsetWidth > 700;
-	const narrow = rootEl.offsetWidth < 400;
+	const wide = rootEl.value.offsetWidth > 700;
+	const narrow = rootEl.value.offsetWidth < 400;
 
 	const weeks = wide ? 50 : narrow ? 10 : 25;
 	const chartLimit = 7 * weeks;
@@ -88,7 +88,7 @@ async function renderChart() {
 		values = raw.deliverFailed;
 	}
 
-	fetching = false;
+	fetching.value = false;
 
 	await nextTick();
 
@@ -101,7 +101,7 @@ async function renderChart() {
 
 	const marginEachCell = 4;
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'matrix',
 		data: {
 			datasets: [{
@@ -210,7 +210,7 @@ async function renderChart() {
 }
 
 watch(() => props.src, () => {
-	fetching = true;
+	fetching.value = true;
 	renderChart();
 });
 
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 4fb573fdbc..942861e1f4 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -21,7 +21,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
-import { $ref } from 'vue/macros';
 import DrawBlurhash from '@/workers/draw-blurhash?worker';
 import TestWebGL2 from '@/workers/test-webgl2?worker';
 import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch.js';
@@ -58,7 +57,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
 </script>
 
 <script lang="ts" setup>
-import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
+import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import { render } from 'buraha';
 import { defaultStore } from '@/store.js';
@@ -98,41 +97,41 @@ const viewId = uuid();
 const canvas = shallowRef<HTMLCanvasElement>();
 const root = shallowRef<HTMLDivElement>();
 const img = shallowRef<HTMLImageElement>();
-let loaded = $ref(false);
-let canvasWidth = $ref(64);
-let canvasHeight = $ref(64);
-let imgWidth = $ref(props.width);
-let imgHeight = $ref(props.height);
-let bitmapTmp = $ref<CanvasImageSource | undefined>();
-const hide = computed(() => !loaded || props.forceBlurhash);
+const loaded = ref(false);
+const canvasWidth = ref(64);
+const canvasHeight = ref(64);
+const imgWidth = ref(props.width);
+const imgHeight = ref(props.height);
+const bitmapTmp = ref<CanvasImageSource | undefined>();
+const hide = computed(() => !loaded.value || props.forceBlurhash);
 
 function waitForDecode() {
 	if (props.src != null && props.src !== '') {
 		nextTick()
 			.then(() => img.value?.decode())
 			.then(() => {
-				loaded = true;
+				loaded.value = true;
 			}, error => {
 				console.log('Error occurred during decoding image', img.value, error);
 			});
 	} else {
-		loaded = false;
+		loaded.value = false;
 	}
 }
 
 watch([() => props.width, () => props.height, root], () => {
 	const ratio = props.width / props.height;
 	if (ratio > 1) {
-		canvasWidth = Math.round(64 * ratio);
-		canvasHeight = 64;
+		canvasWidth.value = Math.round(64 * ratio);
+		canvasHeight.value = 64;
 	} else {
-		canvasWidth = 64;
-		canvasHeight = Math.round(64 / ratio);
+		canvasWidth.value = 64;
+		canvasHeight.value = Math.round(64 / ratio);
 	}
 
 	const clientWidth = root.value?.clientWidth ?? 300;
-	imgWidth = clientWidth;
-	imgHeight = Math.round(clientWidth / ratio);
+	imgWidth.value = clientWidth;
+	imgHeight.value = Math.round(clientWidth / ratio);
 }, {
 	immediate: true,
 });
@@ -140,15 +139,15 @@ watch([() => props.width, () => props.height, root], () => {
 function drawImage(bitmap: CanvasImageSource) {
 	// canvasがない(mountedされていない)場合はTmpに保存しておく
 	if (!canvas.value) {
-		bitmapTmp = bitmap;
+		bitmapTmp.value = bitmap;
 		return;
 	}
 
 	// canvasがあれば描画する
-	bitmapTmp = undefined;
+	bitmapTmp.value = undefined;
 	const ctx = canvas.value.getContext('2d');
 	if (!ctx) return;
-	ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
+	ctx.drawImage(bitmap, 0, 0, canvasWidth.value, canvasHeight.value);
 }
 
 function drawAvg() {
@@ -160,7 +159,7 @@ function drawAvg() {
 	// avgColorでお茶をにごす
 	ctx.beginPath();
 	ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888';
-	ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+	ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value);
 }
 
 async function draw() {
@@ -212,8 +211,8 @@ watch(() => props.hash, () => {
 
 onMounted(() => {
 	// drawImageがmountedより先に呼ばれている場合はここで描画する
-	if (bitmapTmp) {
-		drawImage(bitmapTmp);
+	if (bitmapTmp.value) {
+		drawImage(bitmapTmp.value);
 	}
 	waitForDecode();
 });
diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue
index 9710f779d5..8a63e0cced 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.vue
+++ b/packages/frontend/src/components/MkInstanceCardMini.vue
@@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkMiniChart from '@/components/MkMiniChart.vue';
 import * as os from '@/os.js';
@@ -24,12 +25,12 @@ const props = defineProps<{
 	instance: Misskey.entities.FederationInstance;
 }>();
 
-let chartValues = $ref<number[] | null>(null);
+const chartValues = ref<number[] | null>(null);
 
 os.apiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
 	// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く
 	res['requests.received'].splice(0, 1);
-	chartValues = res['requests.received'];
+	chartValues.value = res['requests.received'];
 });
 
 function getInstanceIcon(instance): string {
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index 509254de74..7b763ad385 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref, shallowRef } from 'vue';
 import { Chart } from 'chart.js';
 import MkSelect from '@/components/MkSelect.vue';
 import MkChart from '@/components/MkChart.vue';
@@ -100,11 +100,11 @@ import { initChart } from '@/scripts/init-chart.js';
 initChart();
 
 const chartLimit = 500;
-let chartSpan = $ref<'hour' | 'day'>('hour');
-let chartSrc = $ref('active-users');
-let heatmapSrc = $ref('active-users');
-let subDoughnutEl = $shallowRef<HTMLCanvasElement>();
-let pubDoughnutEl = $shallowRef<HTMLCanvasElement>();
+const chartSpan = ref<'hour' | 'day'>('hour');
+const chartSrc = ref('active-users');
+const heatmapSrc = ref('active-users');
+const subDoughnutEl = shallowRef<HTMLCanvasElement>();
+const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
 
 const { handler: externalTooltipHandler1 } = useChartTooltip({
 	position: 'middle',
@@ -163,7 +163,7 @@ function createDoughnut(chartEl, tooltip, data) {
 
 onMounted(() => {
 	os.apiGet('federation/stats', { limit: 30 }).then(fedStats => {
-		createDoughnut(subDoughnutEl, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
+		createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followersCount,
@@ -172,7 +172,7 @@ onMounted(() => {
 			},
 		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
 
-		createDoughnut(pubDoughnutEl, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
+		createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followingCount,
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index 1a4f2bfbb9..3ee2aa7174 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import { instanceName } from '@/config.js';
 import { instance as Instance } from '@/instance.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
@@ -30,7 +30,7 @@ const instance = props.instance ?? {
 	themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content,
 };
 
-const faviconUrl = $computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico');
+const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico');
 
 const themeColor = instance.themeColor ?? '#777777';
 
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index b16c05f575..120ed7a86c 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import { navbarItemDef } from '@/navbar.js';
 import { defaultStore } from '@/store.js';
@@ -48,7 +48,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
 	deviceKind === 'smartphone' ? 'drawer' :
 	'dialog';
 
-const modal = $shallowRef<InstanceType<typeof MkModal>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 const menu = defaultStore.state.menu;
 
@@ -63,7 +63,7 @@ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k =>
 }));
 
 function close() {
-	modal.close();
+	modal.value.close();
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index 0501d797f8..808a071d10 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
 import { url as local } from '@/config.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import * as os from '@/os.js';
@@ -29,13 +29,13 @@ const self = props.url.startsWith(local);
 const attr = self ? 'to' : 'href';
 const target = self ? null : '_blank';
 
-const el = $ref();
+const el = ref();
 
-useTooltip($$(el), (showing) => {
+useTooltip(el, (showing) => {
 	os.popup(defineAsyncComponent(() => import('@/components/MkUrlPreviewPopup.vue')), {
 		showing,
 		url: props.url,
-		source: el,
+		source: el.value,
 	}, {}, 'closed');
 });
 </script>
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index 122f8ad794..92b5388c34 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef, watch } from 'vue';
+import { onMounted, shallowRef, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 
@@ -42,7 +42,7 @@ const props = withDefaults(defineProps<{
 });
 
 const audioEl = shallowRef<HTMLAudioElement>();
-let hide = $ref(true);
+const hide = ref(true);
 
 watch(audioEl, () => {
 	if (audioEl.value) {
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index 003c0979c7..d236b222aa 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
 import bytes from '@/filters/bytes.js';
@@ -73,10 +73,10 @@ const props = withDefaults(defineProps<{
 	controls: true,
 });
 
-let hide = $ref(true);
-let darkMode: boolean = $ref(defaultStore.state.darkMode);
+const hide = ref(true);
+const darkMode = ref<boolean>(defaultStore.state.darkMode);
 
-const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
+const url = computed(() => (props.raw || defaultStore.state.loadRawImages)
 	? props.image.url
 	: defaultStore.state.disableShowingAnimatedImages
 		? getStaticImageUrl(props.image.url)
@@ -87,14 +87,14 @@ function onclick() {
 	if (!props.controls) {
 		return;
 	}
-	if (hide) {
-		hide = false;
+	if (hide.value) {
+		hide.value = false;
 	}
 }
 
 // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
 watch(() => props.image, () => {
-	hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
+	hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
 }, {
 	deep: true,
 	immediate: true,
@@ -105,7 +105,7 @@ function showMenu(ev: MouseEvent) {
 		text: i18n.ts.hide,
 		icon: 'ti ti-eye-off',
 		action: () => {
-			hide = true;
+			hide.value = true;
 		},
 	}, ...(iAmModerator ? [{
 		text: i18n.ts.markAsSensitive,
@@ -126,7 +126,7 @@ function showMenu(ev: MouseEvent) {
 
 .sensitive {
 	position: relative;
-	
+
 	&::after {
 		content: "";
 		position: absolute;
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index f134f2021d..b154eb0202 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -63,7 +63,7 @@ async function getClientWidthWithCache(targetEl: HTMLElement, containerEl: HTMLE
 </script>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef } from 'vue';
+import { computed, onMounted, onUnmounted, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import PhotoSwipeLightbox from 'photoswipe/lightbox';
 import PhotoSwipe from 'photoswipe';
@@ -86,7 +86,7 @@ const container = shallowRef<HTMLElement | null | undefined>(undefined);
 const gallery = shallowRef<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);
+const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
 let lightbox: PhotoSwipeLightbox | null;
 
 const popstateHandler = (): void => {
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 736f48ea3c..951a0b2815 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
-import { Ref, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import { Ref, computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 import { focusPrev, focusNext } from '@/scripts/focus.js';
 import MkSwitchButton from '@/components/MkSwitch.button.vue';
 import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu';
@@ -90,19 +90,19 @@ const emit = defineEmits<{
 	(ev: 'hide'): void;
 }>();
 
-let itemsEl = $shallowRef<HTMLDivElement>();
+const itemsEl = shallowRef<HTMLDivElement>();
 
-let items2: InnerMenuItem[] = $ref([]);
+const items2 = ref<InnerMenuItem[]>([]);
 
-let child = $shallowRef<InstanceType<typeof XChild>>();
+const child = shallowRef<InstanceType<typeof XChild>>();
 
-let keymap = $computed(() => ({
+const keymap = computed(() => ({
 	'up|k|shift+tab': focusUp,
 	'down|j|tab': focusDown,
 	'esc': close,
 }));
 
-let childShowingItem = $ref<MenuItem | null>();
+const childShowingItem = ref<MenuItem | null>();
 
 let preferClick = isTouchUsing || props.asDrawer;
 
@@ -115,22 +115,22 @@ watch(() => props.items, () => {
 		if (item && 'then' in item) { // if item is Promise
 			items[i] = { type: 'pending' };
 			item.then(actualItem => {
-				items2[i] = actualItem;
+				items2.value[i] = actualItem;
 			});
 		}
 	}
 
-	items2 = items as InnerMenuItem[];
+	items2.value = items as InnerMenuItem[];
 }, {
 	immediate: true,
 });
 
 const childMenu = ref<MenuItem[] | null>();
-let childTarget = $shallowRef<HTMLElement | null>();
+const childTarget = shallowRef<HTMLElement | null>();
 
 function closeChild() {
 	childMenu.value = null;
-	childShowingItem = null;
+	childShowingItem.value = null;
 }
 
 function childActioned() {
@@ -139,8 +139,8 @@ function childActioned() {
 }
 
 const onGlobalMousedown = (event: MouseEvent) => {
-	if (childTarget && (event.target === childTarget || childTarget.contains(event.target))) return;
-	if (child && child.checkHit(event)) return;
+	if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target))) return;
+	if (child.value && child.value.checkHit(event)) return;
 	closeChild();
 };
 
@@ -177,10 +177,10 @@ async function showChildren(item: MenuParent, ev: MouseEvent) {
 		});
 		emit('hide');
 	} else {
-		childTarget = ev.currentTarget ?? ev.target;
+		childTarget.value = ev.currentTarget ?? ev.target;
 		// これでもリアクティビティは保たれる
 		childMenu.value = children;
-		childShowingItem = item;
+		childShowingItem.value = item;
 	}
 }
 
@@ -209,7 +209,7 @@ function switchItem(item: MenuSwitch & { ref: any }) {
 onMounted(() => {
 	if (props.viaKeyboard) {
 		nextTick(() => {
-			if (itemsEl) focusNext(itemsEl.children[0], true, false);
+			if (itemsEl.value) focusNext(itemsEl.value.children[0], true, false);
 		});
 	}
 
diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue
index 8d2a147306..f0a2c232bd 100644
--- a/packages/frontend/src/components/MkMiniChart.vue
+++ b/packages/frontend/src/components/MkMiniChart.vue
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import tinycolor from 'tinycolor2';
 import { useInterval } from '@/scripts/use-interval.js';
@@ -43,11 +43,11 @@ const props = defineProps<{
 const viewBoxX = 50;
 const viewBoxY = 50;
 const gradientId = uuid();
-let polylinePoints = $ref('');
-let polygonPoints = $ref('');
-let headX = $ref<number | null>(null);
-let headY = $ref<number | null>(null);
-let clock = $ref<number | null>(null);
+const polylinePoints = ref('');
+const polygonPoints = ref('');
+const headX = ref<number | null>(null);
+const headY = ref<number | null>(null);
+const clock = ref<number | null>(null);
 const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
 const color = accent.toRgbString();
 
@@ -60,12 +60,12 @@ function draw(): void {
 		(1 - (n / peak)) * viewBoxY,
 	]);
 
-	polylinePoints = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	polylinePoints.value = _polylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 
-	polygonPoints = `0,${ viewBoxY } ${ polylinePoints } ${ viewBoxX },${ viewBoxY }`;
+	polygonPoints.value = `0,${ viewBoxY } ${ polylinePoints.value } ${ viewBoxX },${ viewBoxY }`;
 
-	headX = _polylinePoints.at(-1)![0];
-	headY = _polylinePoints.at(-1)![1];
+	headX.value = _polylinePoints.at(-1)![0];
+	headY.value = _polylinePoints.at(-1)![1];
 }
 
 watch(() => props.src, draw, { immediate: true });
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index ec5039c504..5cd31cdf7c 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue';
+import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, shallowRef, computed } from 'vue';
 import * as os from '@/os.js';
 import { isTouchUsing } from '@/scripts/touch.js';
 import { defaultStore } from '@/store.js';
@@ -89,14 +89,14 @@ const emit = defineEmits<{
 
 provide('modal', true);
 
-let maxHeight = $ref<number>();
-let fixed = $ref(false);
-let transformOrigin = $ref('center');
-let showing = $ref(true);
-let content = $shallowRef<HTMLElement>();
+const maxHeight = ref<number>();
+const fixed = ref(false);
+const transformOrigin = ref('center');
+const showing = ref(true);
+const content = shallowRef<HTMLElement>();
 const zIndex = os.claimZIndex(props.zPriority);
-let useSendAnime = $ref(false);
-const type = $computed<ModalTypes>(() => {
+const useSendAnime = ref(false);
+const type = computed<ModalTypes>(() => {
 	if (props.preferType === 'auto') {
 		if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
 			return 'drawer';
@@ -107,26 +107,26 @@ const type = $computed<ModalTypes>(() => {
 		return props.preferType!;
 	}
 });
-const isEnableBgTransparent = $computed(() => props.transparentBg && (type === 'popup'));
-let transitionName = $computed((() =>
+const isEnableBgTransparent = computed(() => props.transparentBg && (type.value === 'popup'));
+const transitionName = computed((() =>
 	defaultStore.state.animation
-		? useSendAnime
+		? useSendAnime.value
 			? 'send'
-			: type === 'drawer'
+			: type.value === 'drawer'
 				? 'modal-drawer'
-				: type === 'popup'
+				: type.value === 'popup'
 					? 'modal-popup'
 					: 'modal'
 		: ''
 ));
-let transitionDuration = $computed((() =>
-	transitionName === 'send'
+const transitionDuration = computed((() =>
+	transitionName.value === 'send'
 		? 400
-		: transitionName === 'modal-popup'
+		: transitionName.value === 'modal-popup'
 			? 100
-			: transitionName === 'modal'
+			: transitionName.value === 'modal'
 				? 200
-				: transitionName === 'modal-drawer'
+				: transitionName.value === 'modal-drawer'
 					? 200
 					: 0
 ));
@@ -135,12 +135,12 @@ let contentClicking = false;
 
 function close(opts: { useSendAnimation?: boolean } = {}) {
 	if (opts.useSendAnimation) {
-		useSendAnime = true;
+		useSendAnime.value = true;
 	}
 
 	// eslint-disable-next-line vue/no-mutating-props
 	if (props.src) props.src.style.pointerEvents = 'auto';
-	showing = false;
+	showing.value = false;
 	emit('close');
 }
 
@@ -149,8 +149,8 @@ function onBgClick() {
 	emit('click');
 }
 
-if (type === 'drawer') {
-	maxHeight = window.innerHeight / 1.5;
+if (type.value === 'drawer') {
+	maxHeight.value = window.innerHeight / 1.5;
 }
 
 const keymap = {
@@ -162,21 +162,21 @@ const SCROLLBAR_THICKNESS = 16;
 
 const align = () => {
 	if (props.src == null) return;
-	if (type === 'drawer') return;
-	if (type === 'dialog') return;
+	if (type.value === 'drawer') return;
+	if (type.value === 'dialog') return;
 
-	if (content == null) return;
+	if (content.value == null) return;
 
 	const srcRect = props.src.getBoundingClientRect();
 
-	const width = content!.offsetWidth;
-	const height = content!.offsetHeight;
+	const width = content.value!.offsetWidth;
+	const height = content.value!.offsetHeight;
 
 	let left;
 	let top;
 
-	const x = srcRect.left + (fixed ? 0 : window.pageXOffset);
-	const y = srcRect.top + (fixed ? 0 : window.pageYOffset);
+	const x = srcRect.left + (fixed.value ? 0 : window.pageXOffset);
+	const y = srcRect.top + (fixed.value ? 0 : window.pageYOffset);
 
 	if (props.anchor.x === 'center') {
 		left = x + (props.src.offsetWidth / 2) - (width / 2);
@@ -194,7 +194,7 @@ const align = () => {
 		top = y + props.src.offsetHeight;
 	}
 
-	if (fixed) {
+	if (fixed.value) {
 		// 画面から横にはみ出る場合
 		if (left + width > (window.innerWidth - SCROLLBAR_THICKNESS)) {
 			left = (window.innerWidth - SCROLLBAR_THICKNESS) - width;
@@ -207,16 +207,16 @@ const align = () => {
 		if (top + height > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
 			if (props.noOverlap && props.anchor.x === 'center') {
 				if (underSpace >= (upperSpace / 3)) {
-					maxHeight = underSpace;
+					maxHeight.value = underSpace;
 				} else {
-					maxHeight = upperSpace;
+					maxHeight.value = upperSpace;
 					top = (upperSpace + MARGIN) - height;
 				}
 			} else {
 				top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height;
 			}
 		} else {
-			maxHeight = underSpace;
+			maxHeight.value = underSpace;
 		}
 	} else {
 		// 画面から横にはみ出る場合
@@ -231,16 +231,16 @@ const align = () => {
 		if (top + height - window.pageYOffset > ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN)) {
 			if (props.noOverlap && props.anchor.x === 'center') {
 				if (underSpace >= (upperSpace / 3)) {
-					maxHeight = underSpace;
+					maxHeight.value = underSpace;
 				} else {
-					maxHeight = upperSpace;
+					maxHeight.value = upperSpace;
 					top = window.pageYOffset + ((upperSpace + MARGIN) - height);
 				}
 			} else {
 				top = ((window.innerHeight - SCROLLBAR_THICKNESS) - MARGIN) - height + window.pageYOffset - 1;
 			}
 		} else {
-			maxHeight = underSpace;
+			maxHeight.value = underSpace;
 		}
 	}
 
@@ -255,29 +255,29 @@ const align = () => {
 	let transformOriginX = 'center';
 	let transformOriginY = 'center';
 
-	if (top >= srcRect.top + props.src.offsetHeight + (fixed ? 0 : window.pageYOffset)) {
+	if (top >= srcRect.top + props.src.offsetHeight + (fixed.value ? 0 : window.pageYOffset)) {
 		transformOriginY = 'top';
-	} else if ((top + height) <= srcRect.top + (fixed ? 0 : window.pageYOffset)) {
+	} else if ((top + height) <= srcRect.top + (fixed.value ? 0 : window.pageYOffset)) {
 		transformOriginY = 'bottom';
 	}
 
-	if (left >= srcRect.left + props.src.offsetWidth + (fixed ? 0 : window.pageXOffset)) {
+	if (left >= srcRect.left + props.src.offsetWidth + (fixed.value ? 0 : window.pageXOffset)) {
 		transformOriginX = 'left';
-	} else if ((left + width) <= srcRect.left + (fixed ? 0 : window.pageXOffset)) {
+	} else if ((left + width) <= srcRect.left + (fixed.value ? 0 : window.pageXOffset)) {
 		transformOriginX = 'right';
 	}
 
-	transformOrigin = `${transformOriginX} ${transformOriginY}`;
+	transformOrigin.value = `${transformOriginX} ${transformOriginY}`;
 
-	content.style.left = left + 'px';
-	content.style.top = top + 'px';
+	content.value.style.left = left + 'px';
+	content.value.style.top = top + 'px';
 };
 
 const onOpened = () => {
 	emit('opened');
 
 	// モーダルコンテンツにマウスボタンが押され、コンテンツ外でマウスボタンが離されたときにモーダルバックグラウンドクリックと判定させないためにマウスイベントを監視しフラグ管理する
-	const el = content!.children[0];
+	const el = content.value!.children[0];
 	el.addEventListener('mousedown', ev => {
 		contentClicking = true;
 		window.addEventListener('mouseup', ev => {
@@ -299,7 +299,7 @@ onMounted(() => {
 			// eslint-disable-next-line vue/no-mutating-props
 			props.src.style.pointerEvents = 'none';
 		}
-		fixed = (type === 'drawer') || (getFixedContainer(props.src) != null);
+		fixed.value = (type.value === 'drawer') || (getFixedContainer(props.src) != null);
 
 		await nextTick();
 
@@ -307,7 +307,7 @@ onMounted(() => {
 	}, { immediate: true });
 
 	nextTick(() => {
-		alignObserver.observe(content!);
+		alignObserver.observe(content.value!);
 	});
 });
 
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index 1fdf0beb26..7e185e8453 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted } from 'vue';
+import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
 import MkModal from './MkModal.vue';
 
 const props = withDefaults(defineProps<{
@@ -44,14 +44,14 @@ const emit = defineEmits<{
 	(event: 'ok'): void;
 }>();
 
-let modal = $shallowRef<InstanceType<typeof MkModal>>();
-let rootEl = $shallowRef<HTMLElement>();
-let headerEl = $shallowRef<HTMLElement>();
-let bodyWidth = $ref(0);
-let bodyHeight = $ref(0);
+const modal = shallowRef<InstanceType<typeof MkModal>>();
+const rootEl = shallowRef<HTMLElement>();
+const headerEl = shallowRef<HTMLElement>();
+const bodyWidth = ref(0);
+const bodyHeight = ref(0);
 
 const close = () => {
-	modal.close();
+	modal.value.close();
 };
 
 const onBgClick = () => {
@@ -67,14 +67,14 @@ const onKeydown = (evt) => {
 };
 
 const ro = new ResizeObserver((entries, observer) => {
-	bodyWidth = rootEl.offsetWidth;
-	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
+	bodyWidth.value = rootEl.value.offsetWidth;
+	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
 });
 
 onMounted(() => {
-	bodyWidth = rootEl.offsetWidth;
-	bodyHeight = rootEl.offsetHeight - headerEl.offsetHeight;
-	ro.observe(rootEl);
+	bodyWidth.value = rootEl.value.offsetWidth;
+	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
+	ro.observe(rootEl.value);
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 596895efb9..36e3b253a2 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -205,12 +205,12 @@ const emit = defineEmits<{
 const inChannel = inject('inChannel', null);
 const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
 
-let note = $ref(deepClone(props.note));
+const note = ref(deepClone(props.note));
 
 // plugin
 if (noteViewInterruptors.length > 0) {
 	onMounted(async () => {
-		let result: Misskey.entities.Note | null = deepClone(note);
+		let result: Misskey.entities.Note | null = deepClone(note.value);
 		for (const interruptor of noteViewInterruptors) {
 			try {
 				result = await interruptor.handler(result);
@@ -222,15 +222,15 @@ if (noteViewInterruptors.length > 0) {
 				console.error(err);
 			}
 		}
-		note = result;
+		note.value = result;
 	});
 }
 
 const isRenote = (
-	note.renote != null &&
-	note.text == null &&
-	note.fileIds.length === 0 &&
-	note.poll == null
+	note.value.renote != null &&
+	note.value.text == null &&
+	note.value.fileIds.length === 0 &&
+	note.value.poll == null
 );
 
 const el = shallowRef<HTMLElement>();
@@ -239,21 +239,21 @@ const renoteButton = shallowRef<HTMLElement>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
-let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
-const isMyRenote = $i && ($i.id === note.userId);
+const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
+const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(false);
-const parsed = $computed(() => appearNote.text ? mfm.parse(appearNote.text) : null);
-const urls = $computed(() => parsed ? extractUrlFromMfm(parsed) : null);
-const isLong = shouldCollapsed(appearNote, urls ?? []);
-const collapsed = ref(appearNote.cw == null && isLong);
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null);
+const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
+const collapsed = ref(appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
-const muted = ref(checkMute(appearNote, $i?.mutedWords));
-const hardMuted = ref(props.withHardMute && checkMute(appearNote, $i?.hardMutedWords));
+const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
+const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
 const translation = ref<any>(null);
 const translating = ref(false);
-const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || (appearNote.visibility === 'followers' && appearNote.userId === $i.id));
-let renoteCollapsed = $ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.userId || $i.id === appearNote.userId)) || (appearNote.myReaction != null)));
+const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));
+const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null)));
 
 function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean {
 	if (mutedWords == null) return false;
@@ -277,20 +277,20 @@ const keymap = {
 
 provide('react', (reaction: string) => {
 	os.api('notes/reactions/create', {
-		noteId: appearNote.id,
+		noteId: appearNote.value.id,
 		reaction: reaction,
 	});
 });
 
 if (props.mock) {
 	watch(() => props.note, (to) => {
-		note = deepClone(to);
+		note.value = deepClone(to);
 	}, { deep: true });
 } else {
 	useNoteCapture({
 		rootEl: el,
-		note: $$(appearNote),
-		pureNote: $$(note),
+		note: appearNote,
+		pureNote: note,
 		isDeletedRef: isDeleted,
 	});
 }
@@ -298,7 +298,7 @@ if (props.mock) {
 if (!props.mock) {
 	useTooltip(renoteButton, async (showing) => {
 		const renotes = await os.api('notes/renotes', {
-			noteId: appearNote.id,
+			noteId: appearNote.value.id,
 			limit: 11,
 		});
 
@@ -309,7 +309,7 @@ if (!props.mock) {
 		os.popup(MkUsersTooltip, {
 			showing,
 			users,
-			count: appearNote.renoteCount,
+			count: appearNote.value.renoteCount,
 			targetElement: renoteButton.value,
 		}, {}, 'closed');
 	});
@@ -319,7 +319,7 @@ function renote(viaKeyboard = false) {
 	pleaseLogin();
 	showMovedDialog();
 
-	const { menu } = getRenoteMenu({ note: note, renoteButton, mock: props.mock });
+	const { menu } = getRenoteMenu({ note: note.value, renoteButton, mock: props.mock });
 	os.popupMenu(menu, renoteButton.value, {
 		viaKeyboard,
 	});
@@ -331,8 +331,8 @@ function reply(viaKeyboard = false): void {
 		return;
 	}
 	os.post({
-		reply: appearNote,
-		channel: appearNote.channel,
+		reply: appearNote.value,
+		channel: appearNote.value.channel,
 		animation: !viaKeyboard,
 	}, () => {
 		focus();
@@ -342,7 +342,7 @@ function reply(viaKeyboard = false): void {
 function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
-	if (appearNote.reactionAcceptance === 'likeOnly') {
+	if (appearNote.value.reactionAcceptance === 'likeOnly') {
 		sound.play('reaction');
 
 		if (props.mock) {
@@ -350,7 +350,7 @@ function react(viaKeyboard = false): void {
 		}
 
 		os.api('notes/reactions/create', {
-			noteId: appearNote.id,
+			noteId: appearNote.value.id,
 			reaction: '❤️',
 		});
 		const el = reactButton.value as HTMLElement | null | undefined;
@@ -371,10 +371,10 @@ function react(viaKeyboard = false): void {
 			}
 
 			os.api('notes/reactions/create', {
-				noteId: appearNote.id,
+				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
-			if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
+			if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
 				claimAchievement('reactWithoutRead');
 			}
 		}, () => {
@@ -417,7 +417,7 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
 		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
@@ -427,7 +427,7 @@ function menu(viaKeyboard = false): void {
 		return;
 	}
 
-	const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
 	os.popupMenu(menu, menuButton.value, {
 		viaKeyboard,
 	}).then(focus).finally(cleanup);
@@ -438,7 +438,7 @@ async function clip() {
 		return;
 	}
 
-	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
+	os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted, currentClip: currentClip?.value }), clipButton.value).then(focus);
 }
 
 function showRenoteMenu(viaKeyboard = false): void {
@@ -453,7 +453,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 			danger: true,
 			action: () => {
 				os.api('notes/delete', {
-					noteId: note.id,
+					noteId: note.value.id,
 				});
 				isDeleted.value = true;
 			},
@@ -463,7 +463,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 	if (isMyRenote) {
 		pleaseLogin();
 		os.popupMenu([
-			getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
+			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
 			null,
 			getUnrenote(),
 		], renoteTime.value, {
@@ -471,9 +471,9 @@ function showRenoteMenu(viaKeyboard = false): void {
 		});
 	} else {
 		os.popupMenu([
-			getCopyNoteLinkMenu(note, i18n.ts.copyLinkRenote),
+			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
 			null,
-			getAbuseNoteMenu(note, i18n.ts.reportAbuseRenote),
+			getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),
 			$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
 		], renoteTime.value, {
 			viaKeyboard: viaKeyboard,
@@ -499,7 +499,7 @@ function focusAfter() {
 
 function readPromo() {
 	os.api('promo/read', {
-		noteId: appearNote.id,
+		noteId: appearNote.value.id,
 	});
 	isDeleted.value = true;
 }
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 31e97b6aaa..a8ccf26c4c 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -235,12 +235,12 @@ const props = defineProps<{
 
 const inChannel = inject('inChannel', null);
 
-let note = $ref(deepClone(props.note));
+const note = ref(deepClone(props.note));
 
 // plugin
 if (noteViewInterruptors.length > 0) {
 	onMounted(async () => {
-		let result: Misskey.entities.Note | null = deepClone(note);
+		let result: Misskey.entities.Note | null = deepClone(note.value);
 		for (const interruptor of noteViewInterruptors) {
 			try {
 				result = await interruptor.handler(result);
@@ -252,15 +252,15 @@ if (noteViewInterruptors.length > 0) {
 				console.error(err);
 			}
 		}
-		note = result;
+		note.value = result;
 	});
 }
 
 const isRenote = (
-	note.renote != null &&
-	note.text == null &&
-	note.fileIds.length === 0 &&
-	note.poll == null
+	note.value.renote != null &&
+	note.value.text == null &&
+	note.value.fileIds.length === 0 &&
+	note.value.poll == null
 );
 
 const el = shallowRef<HTMLElement>();
@@ -269,19 +269,19 @@ const renoteButton = shallowRef<HTMLElement>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
-let appearNote = $computed(() => isRenote ? note.renote as Misskey.entities.Note : note);
-const isMyRenote = $i && ($i.id === note.userId);
+const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
+const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(false);
 const isDeleted = ref(false);
-const muted = ref($i ? checkWordMute(appearNote, $i, $i.mutedWords) : false);
+const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
 const translation = ref(null);
 const translating = ref(false);
-const parsed = appearNote.text ? mfm.parse(appearNote.text) : null;
+const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
 const urls = parsed ? extractUrlFromMfm(parsed) : null;
-const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
+const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
 const replies = ref<Misskey.entities.Note[]>([]);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i.id);
 
 const keymap = {
 	'r': () => reply(true),
@@ -294,41 +294,41 @@ const keymap = {
 
 provide('react', (reaction: string) => {
 	os.api('notes/reactions/create', {
-		noteId: appearNote.id,
+		noteId: appearNote.value.id,
 		reaction: reaction,
 	});
 });
 
-let tab = $ref('replies');
-let reactionTabType = $ref(null);
+const tab = ref('replies');
+const reactionTabType = ref(null);
 
-const renotesPagination = $computed(() => ({
+const renotesPagination = computed(() => ({
 	endpoint: 'notes/renotes',
 	limit: 10,
 	params: {
-		noteId: appearNote.id,
+		noteId: appearNote.value.id,
 	},
 }));
 
-const reactionsPagination = $computed(() => ({
+const reactionsPagination = computed(() => ({
 	endpoint: 'notes/reactions',
 	limit: 10,
 	params: {
-		noteId: appearNote.id,
-		type: reactionTabType,
+		noteId: appearNote.value.id,
+		type: reactionTabType.value,
 	},
 }));
 
 useNoteCapture({
 	rootEl: el,
-	note: $$(appearNote),
-	pureNote: $$(note),
+	note: appearNote,
+	pureNote: note,
 	isDeletedRef: isDeleted,
 });
 
 useTooltip(renoteButton, async (showing) => {
 	const renotes = await os.api('notes/renotes', {
-		noteId: appearNote.id,
+		noteId: appearNote.value.id,
 		limit: 11,
 	});
 
@@ -339,7 +339,7 @@ useTooltip(renoteButton, async (showing) => {
 	os.popup(MkUsersTooltip, {
 		showing,
 		users,
-		count: appearNote.renoteCount,
+		count: appearNote.value.renoteCount,
 		targetElement: renoteButton.value,
 	}, {}, 'closed');
 });
@@ -348,7 +348,7 @@ function renote(viaKeyboard = false) {
 	pleaseLogin();
 	showMovedDialog();
 
-	const { menu } = getRenoteMenu({ note: note, renoteButton });
+	const { menu } = getRenoteMenu({ note: note.value, renoteButton });
 	os.popupMenu(menu, renoteButton.value, {
 		viaKeyboard,
 	});
@@ -358,8 +358,8 @@ function reply(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
 	os.post({
-		reply: appearNote,
-		channel: appearNote.channel,
+		reply: appearNote.value,
+		channel: appearNote.value.channel,
 		animation: !viaKeyboard,
 	}, () => {
 		focus();
@@ -369,11 +369,11 @@ function reply(viaKeyboard = false): void {
 function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
-	if (appearNote.reactionAcceptance === 'likeOnly') {
+	if (appearNote.value.reactionAcceptance === 'likeOnly') {
 		sound.play('reaction');
 
 		os.api('notes/reactions/create', {
-			noteId: appearNote.id,
+			noteId: appearNote.value.id,
 			reaction: '❤️',
 		});
 		const el = reactButton.value as HTMLElement | null | undefined;
@@ -389,10 +389,10 @@ function react(viaKeyboard = false): void {
 			sound.play('reaction');
 
 			os.api('notes/reactions/create', {
-				noteId: appearNote.id,
+				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
-			if (appearNote.text && appearNote.text.length > 100 && (Date.now() - new Date(appearNote.createdAt).getTime() < 1000 * 3)) {
+			if (appearNote.value.text && appearNote.value.text.length > 100 && (Date.now() - new Date(appearNote.value.createdAt).getTime() < 1000 * 3)) {
 				claimAchievement('reactWithoutRead');
 			}
 		}, () => {
@@ -423,20 +423,20 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted });
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted });
 		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
 function menu(viaKeyboard = false): void {
-	const { menu, cleanup } = getNoteMenu({ note: note, translating, translation, menuButton, isDeleted });
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted });
 	os.popupMenu(menu, menuButton.value, {
 		viaKeyboard,
 	}).then(focus).finally(cleanup);
 }
 
 async function clip() {
-	os.popupMenu(await getNoteClipMenu({ note: note, isDeleted }), clipButton.value).then(focus);
+	os.popupMenu(await getNoteClipMenu({ note: note.value, isDeleted }), clipButton.value).then(focus);
 }
 
 function showRenoteMenu(viaKeyboard = false): void {
@@ -448,7 +448,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 		danger: true,
 		action: () => {
 			os.api('notes/delete', {
-				noteId: note.id,
+				noteId: note.value.id,
 			});
 			isDeleted.value = true;
 		},
@@ -470,7 +470,7 @@ const repliesLoaded = ref(false);
 function loadReplies() {
 	repliesLoaded.value = true;
 	os.api('notes/children', {
-		noteId: appearNote.id,
+		noteId: appearNote.value.id,
 		limit: 30,
 	}).then(res => {
 		replies.value = res;
@@ -482,7 +482,7 @@ const conversationLoaded = ref(false);
 function loadConversation() {
 	conversationLoaded.value = true;
 	os.api('notes/conversation', {
-		noteId: appearNote.replyId,
+		noteId: appearNote.value.replyId,
 	}).then(res => {
 		conversation.value = res.reverse();
 	});
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index f3ab6b2723..868f64a4b8 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
 import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
@@ -33,7 +33,7 @@ const props = defineProps<{
 	note: Misskey.entities.Note;
 }>();
 
-const showContent = $ref(false);
+const showContent = ref(false);
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 1e901a1fd6..5649ce1e6c 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -65,15 +65,15 @@ const props = withDefaults(defineProps<{
 
 const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
 
-let showContent = $ref(false);
-let replies: Misskey.entities.Note[] = $ref([]);
+const showContent = ref(false);
+const replies = ref<Misskey.entities.Note[]>([]);
 
 if (props.detail) {
 	os.api('notes/children', {
 		noteId: props.note.id,
 		limit: 5,
 	}).then(res => {
-		replies = res;
+		replies.value = res;
 	});
 }
 </script>
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index 3d5a56975b..6725776f43 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, Ref } from 'vue';
+import { ref, Ref, shallowRef } from 'vue';
 import MkSwitch from './MkSwitch.vue';
 import MkInfo from './MkInfo.vue';
 import MkButton from './MkButton.vue';
@@ -51,7 +51,7 @@ const props = withDefaults(defineProps<{
 	excludeTypes: () => [],
 });
 
-const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
 const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any);
 
@@ -61,7 +61,7 @@ function ok() {
 			.filter(type => !typesMap[type].value),
 	});
 
-	if (dialog) dialog.close();
+	if (dialog.value) dialog.value.close();
 }
 
 function disableAll() {
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 2a0082204a..cefef91285 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -43,7 +43,7 @@ const props = defineProps<{
 
 const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
-let pagination = $computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? {
+const pagination = computed(() => defaultStore.reactiveState.useGroupedNotifications.value ? {
 	endpoint: 'i/notifications-grouped' as const,
 	limit: 20,
 	params: computed(() => ({
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index 8c113bd777..1b0ec72e41 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted } from 'vue';
+import { onMounted, onUnmounted, shallowRef, ref } from 'vue';
 import { i18n } from '@/i18n.js';
 
 const props = withDefaults(defineProps<{
@@ -22,13 +22,13 @@ const props = withDefaults(defineProps<{
 	maxHeight: 200,
 });
 
-let content = $shallowRef<HTMLElement>();
-let omitted = $ref(false);
-let ignoreOmit = $ref(false);
+const content = shallowRef<HTMLElement>();
+const omitted = ref(false);
+const ignoreOmit = ref(false);
 
 const calcOmit = () => {
-	if (omitted || ignoreOmit) return;
-	omitted = content.offsetHeight > props.maxHeight;
+	if (omitted.value || ignoreOmit.value) return;
+	omitted.value = content.value.offsetHeight > props.maxHeight;
 };
 
 const omitObserver = new ResizeObserver((entries, observer) => {
@@ -37,7 +37,7 @@ const omitObserver = new ResizeObserver((entries, observer) => {
 
 onMounted(() => {
 	calcOmit();
-	omitObserver.observe(content);
+	omitObserver.observe(content.value);
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 1b1eb11444..441296e05d 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, onMounted, onUnmounted, provide, shallowRef } from 'vue';
+import { ComputedRef, onMounted, onUnmounted, provide, shallowRef, ref, computed } from 'vue';
 import RouterView from '@/components/global/RouterView.vue';
 import MkWindow from '@/components/MkWindow.vue';
 import { popout as _popout } from '@/scripts/popout.js';
@@ -55,16 +55,16 @@ defineEmits<{
 const router = new Router(routes, props.initialPath, !!$i, page(() => import('@/pages/not-found.vue')));
 
 const contents = shallowRef<HTMLElement>();
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-let windowEl = $shallowRef<InstanceType<typeof MkWindow>>();
-const history = $ref<{ path: string; key: any; }[]>([{
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
+const history = ref<{ path: string; key: any; }[]>([{
 	path: router.getCurrentPath(),
 	key: router.getCurrentKey(),
 }]);
-const buttonsLeft = $computed(() => {
+const buttonsLeft = computed(() => {
 	const buttons = [];
 
-	if (history.length > 1) {
+	if (history.value.length > 1) {
 		buttons.push({
 			icon: 'ti ti-arrow-left',
 			onClick: back,
@@ -73,7 +73,7 @@ const buttonsLeft = $computed(() => {
 
 	return buttons;
 });
-const buttonsRight = $computed(() => {
+const buttonsRight = computed(() => {
 	const buttons = [{
 		icon: 'ti ti-reload',
 		title: i18n.ts.reload,
@@ -86,21 +86,21 @@ const buttonsRight = $computed(() => {
 
 	return buttons;
 });
-let reloadCount = $ref(0);
+const reloadCount = ref(0);
 
 router.addListener('push', ctx => {
-	history.push({ path: ctx.path, key: ctx.key });
+	history.value.push({ path: ctx.path, key: ctx.key });
 });
 
 provide('router', router);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
+	pageMetadata.value = info;
 });
 provide('shouldOmitHeaderTitle', true);
 provide('shouldHeaderThin', true);
 provide('forceSpacerMin', true);
 
-const contextmenu = $computed(() => ([{
+const contextmenu = computed(() => ([{
 	icon: 'ti ti-player-eject',
 	text: i18n.ts.showInPage,
 	action: expand,
@@ -113,7 +113,7 @@ const contextmenu = $computed(() => ([{
 	text: i18n.ts.openInNewTab,
 	action: () => {
 		window.open(url + router.getCurrentPath(), '_blank');
-		windowEl.close();
+		windowEl.value.close();
 	},
 }, {
 	icon: 'ti ti-link',
@@ -124,26 +124,26 @@ const contextmenu = $computed(() => ([{
 }]));
 
 function back() {
-	history.pop();
-	router.replace(history.at(-1)!.path, history.at(-1)!.key);
+	history.value.pop();
+	router.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
 }
 
 function reload() {
-	reloadCount++;
+	reloadCount.value++;
 }
 
 function close() {
-	windowEl.close();
+	windowEl.value.close();
 }
 
 function expand() {
 	mainRouter.push(router.getCurrentPath(), 'forcePage');
-	windowEl.close();
+	windowEl.value.close();
 }
 
 function popout() {
-	_popout(router.getCurrentPath(), windowEl.$el);
-	windowEl.close();
+	_popout(router.getCurrentPath(), windowEl.value.$el);
+	windowEl.value.close();
 }
 
 useScrollPositionManager(() => getScrollContainer(contents.value), router);
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index 57348cde53..07347eda29 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
-import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, watch } from 'vue';
+import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
 import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
@@ -105,12 +105,12 @@ const emit = defineEmits<{
 	(ev: 'status', error: boolean): void;
 }>();
 
-let rootEl = $shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
 
 // 遡り中かどうか
-let backed = $ref(false);
+const backed = ref(false);
 
-let scrollRemove = $ref<(() => void) | null>(null);
+const scrollRemove = ref<(() => void) | null>(null);
 
 /**
  * 表示するアイテムのソース
@@ -142,8 +142,8 @@ const {
 	enableInfiniteScroll,
 } = defaultStore.reactiveState;
 
-const contentEl = $computed(() => props.pagination.pageEl ?? rootEl);
-const scrollableElement = $computed(() => contentEl ? getScrollContainer(contentEl) : document.body);
+const contentEl = computed(() => props.pagination.pageEl ?? rootEl.value);
+const scrollableElement = computed(() => contentEl.value ? getScrollContainer(contentEl.value) : document.body);
 
 const visibility = useDocumentVisibility();
 
@@ -153,35 +153,35 @@ const BACKGROUND_PAUSE_WAIT_SEC = 10;
 
 // 先頭が表示されているかどうかを検出
 // https://qiita.com/mkataigi/items/0154aefd2223ce23398e
-let scrollObserver = $ref<IntersectionObserver>();
+const scrollObserver = ref<IntersectionObserver>();
 
-watch([() => props.pagination.reversed, $$(scrollableElement)], () => {
-	if (scrollObserver) scrollObserver.disconnect();
+watch([() => props.pagination.reversed, scrollableElement], () => {
+	if (scrollObserver.value) scrollObserver.value.disconnect();
 
-	scrollObserver = new IntersectionObserver(entries => {
-		backed = entries[0].isIntersecting;
+	scrollObserver.value = new IntersectionObserver(entries => {
+		backed.value = entries[0].isIntersecting;
 	}, {
-		root: scrollableElement,
+		root: scrollableElement.value,
 		rootMargin: props.pagination.reversed ? '-100% 0px 100% 0px' : '100% 0px -100% 0px',
 		threshold: 0.01,
 	});
 }, { immediate: true });
 
-watch($$(rootEl), () => {
-	scrollObserver?.disconnect();
+watch(rootEl, () => {
+	scrollObserver.value?.disconnect();
 	nextTick(() => {
-		if (rootEl) scrollObserver?.observe(rootEl);
+		if (rootEl.value) scrollObserver.value?.observe(rootEl.value);
 	});
 });
 
-watch([$$(backed), $$(contentEl)], () => {
-	if (!backed) {
-		if (!contentEl) return;
+watch([backed, contentEl], () => {
+	if (!backed.value) {
+		if (!contentEl.value) return;
 
-		scrollRemove = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl, executeQueue, TOLERANCE);
+		scrollRemove.value = (props.pagination.reversed ? onScrollBottom : onScrollTop)(contentEl.value, executeQueue, TOLERANCE);
 	} else {
-		if (scrollRemove) scrollRemove();
-		scrollRemove = null;
+		if (scrollRemove.value) scrollRemove.value();
+		scrollRemove.value = null;
 	}
 });
 
@@ -254,14 +254,14 @@ const fetchMore = async (): Promise<void> => {
 		}
 
 		const reverseConcat = _res => {
-			const oldHeight = scrollableElement ? scrollableElement.scrollHeight : getBodyScrollHeight();
-			const oldScroll = scrollableElement ? scrollableElement.scrollTop : window.scrollY;
+			const oldHeight = scrollableElement.value ? scrollableElement.value.scrollHeight : getBodyScrollHeight();
+			const oldScroll = scrollableElement.value ? scrollableElement.value.scrollTop : window.scrollY;
 
 			items.value = concatMapWithArray(items.value, _res);
 
 			return nextTick(() => {
-				if (scrollableElement) {
-					scroll(scrollableElement, { top: oldScroll + (scrollableElement.scrollHeight - oldHeight), behavior: 'instant' });
+				if (scrollableElement.value) {
+					scroll(scrollableElement.value, { top: oldScroll + (scrollableElement.value.scrollHeight - oldHeight), behavior: 'instant' });
 				} else {
 					window.scroll({ top: oldScroll + (getBodyScrollHeight() - oldHeight), behavior: 'instant' });
 				}
@@ -351,7 +351,7 @@ const appearFetchMoreAhead = async (): Promise<void> => {
 	fetchMoreAppearTimeout();
 };
 
-const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl!, TOLERANCE);
+const isTop = (): boolean => isBackTop.value || (props.pagination.reversed ? isBottomVisible : isTopVisible)(contentEl.value!, TOLERANCE);
 
 watch(visibility, () => {
 	if (visibility.value === 'hidden') {
@@ -445,11 +445,11 @@ onActivated(() => {
 });
 
 onDeactivated(() => {
-	isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl ? rootEl.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
+	isBackTop.value = props.pagination.reversed ? window.scrollY >= (rootEl.value ? rootEl.value.scrollHeight - window.innerHeight : 0) : window.scrollY === 0;
 });
 
 function toBottom() {
-	scrollToBottom(contentEl!);
+	scrollToBottom(contentEl.value!);
 }
 
 onBeforeMount(() => {
@@ -477,13 +477,13 @@ onBeforeUnmount(() => {
 		clearTimeout(preventAppearFetchMoreTimer.value);
 		preventAppearFetchMoreTimer.value = null;
 	}
-	scrollObserver?.disconnect();
+	scrollObserver.value?.disconnect();
 });
 
 defineExpose({
 	items,
 	queue,
-	backed,
+	backed: backed.value,
 	more,
 	reload,
 	prepend,
diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue
index 3abca7826f..118f9a6a91 100644
--- a/packages/frontend/src/components/MkPasswordDialog.vue
+++ b/packages/frontend/src/components/MkPasswordDialog.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -49,22 +49,22 @@ const emit = defineEmits<{
 	(ev: 'cancelled'): void;
 }>();
 
-const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
-const passwordInput = $shallowRef<InstanceType<typeof MkInput>>();
-const password = $ref('');
-const token = $ref(null);
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
+const password = ref('');
+const token = ref(null);
 
 function onClose() {
 	emit('cancelled');
-	if (dialog) dialog.close();
+	if (dialog.value) dialog.value.close();
 }
 
 function done(res) {
-	emit('done', { password, token });
-	if (dialog) dialog.close();
+	emit('done', { password: password.value, token: token.value });
+	if (dialog.value) dialog.value.close();
 }
 
 onMounted(() => {
-	if (passwordInput) passwordInput.focus();
+	if (passwordInput.value) passwordInput.value.focus();
 });
 </script>
diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue
index 0bc98f4334..a741a3f7a8 100644
--- a/packages/frontend/src/components/MkPlusOneEffect.vue
+++ b/packages/frontend/src/components/MkPlusOneEffect.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as os from '@/os.js';
 
 const props = withDefaults(defineProps<{
@@ -23,13 +23,13 @@ const emit = defineEmits<{
 	(ev: 'end'): void;
 }>();
 
-let up = $ref(false);
+const up = ref(false);
 const zIndex = os.claimZIndex('middle');
 const angle = (45 - (Math.random() * 90)) + 'deg';
 
 onMounted(() => {
 	window.setTimeout(() => {
-		up = true;
+		up.value = true;
 	}, 10);
 
 	window.setTimeout(() => {
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index ee7dbecf05..67fac003bf 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, shallowRef } from 'vue';
 import MkModal from './MkModal.vue';
 import MkMenu from './MkMenu.vue';
 import { MenuItem } from '@/types/menu';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
 	(ev: 'closing'): void;
 }>();
 
-let modal = $shallowRef<InstanceType<typeof MkModal>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 const manualShowing = ref(true);
 const hiding = ref(false);
 
@@ -60,14 +60,14 @@ function hide() {
 	hiding.value = true;
 
 	// closeは呼ぶ必要がある
-	modal?.close();
+	modal.value?.close();
 }
 
 function close() {
 	manualShowing.value = false;
 
 	// closeは呼ぶ必要がある
-	modal?.close();
+	modal.value?.close();
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 13b615d104..9e5c4ca3f1 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -98,7 +98,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, ref } from 'vue';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import insertTextAtCursor from 'insert-text-at-cursor';
@@ -162,42 +162,42 @@ const emit = defineEmits<{
 	(ev: 'fileChangeSensitive', fileId: string, to: boolean): void;
 }>();
 
-const textareaEl = $shallowRef<HTMLTextAreaElement | null>(null);
-const cwInputEl = $shallowRef<HTMLInputElement | null>(null);
-const hashtagsInputEl = $shallowRef<HTMLInputElement | null>(null);
-const visibilityButton = $shallowRef<HTMLElement | null>(null);
+const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
+const cwInputEl = shallowRef<HTMLInputElement | null>(null);
+const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
+const visibilityButton = shallowRef<HTMLElement | null>(null);
 
-let posting = $ref(false);
-let posted = $ref(false);
-let text = $ref(props.initialText ?? '');
-let files = $ref(props.initialFiles ?? []);
-let poll = $ref<{
+const posting = ref(false);
+const posted = ref(false);
+const text = ref(props.initialText ?? '');
+const files = ref(props.initialFiles ?? []);
+const poll = ref<{
 	choices: string[];
 	multiple: boolean;
 	expiresAt: string | null;
 	expiredAfter: string | null;
 } | null>(null);
-let useCw = $ref(false);
-let showPreview = $ref(defaultStore.state.showPreview);
-watch($$(showPreview), () => defaultStore.set('showPreview', showPreview));
-let cw = $ref<string | null>(null);
-let localOnly = $ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
-let visibility = $ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
-let visibleUsers = $ref([]);
+const useCw = ref(false);
+const showPreview = ref(defaultStore.state.showPreview);
+watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
+const cw = ref<string | null>(null);
+const localOnly = ref<boolean>(props.initialLocalOnly ?? defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
+const visibility = ref(props.initialVisibility ?? (defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility) as typeof Misskey.noteVisibilities[number]);
+const visibleUsers = ref([]);
 if (props.initialVisibleUsers) {
 	props.initialVisibleUsers.forEach(pushVisibleUser);
 }
-let reactionAcceptance = $ref(defaultStore.state.reactionAcceptance);
-let autocomplete = $ref(null);
-let draghover = $ref(false);
-let quoteId = $ref(null);
-let hasNotSpecifiedMentions = $ref(false);
-let recentHashtags = $ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
-let imeText = $ref('');
-let showingOptions = $ref(false);
+const reactionAcceptance = ref(defaultStore.state.reactionAcceptance);
+const autocomplete = ref(null);
+const draghover = ref(false);
+const quoteId = ref(null);
+const hasNotSpecifiedMentions = ref(false);
+const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]'));
+const imeText = ref('');
+const showingOptions = ref(false);
 const textAreaReadOnly = ref(false);
 
-const draftKey = $computed((): string => {
+const draftKey = computed((): string => {
 	let key = props.channel ? `channel:${props.channel.id}` : '';
 
 	if (props.renote) {
@@ -211,7 +211,7 @@ const draftKey = $computed((): string => {
 	return key;
 });
 
-const placeholder = $computed((): string => {
+const placeholder = computed((): string => {
 	if (props.renote) {
 		return i18n.ts._postForm.quotePlaceholder;
 	} else if (props.reply) {
@@ -231,7 +231,7 @@ const placeholder = $computed((): string => {
 	}
 });
 
-const submitText = $computed((): string => {
+const submitText = computed((): string => {
 	return props.renote
 		? i18n.ts.quote
 		: props.reply
@@ -239,45 +239,45 @@ const submitText = $computed((): string => {
 			: i18n.ts.note;
 });
 
-const textLength = $computed((): number => {
-	return (text + imeText).trim().length;
+const textLength = computed((): number => {
+	return (text.value + imeText.value).trim().length;
 });
 
-const maxTextLength = $computed((): number => {
+const maxTextLength = computed((): number => {
 	return instance ? instance.maxNoteTextLength : 1000;
 });
 
-const canPost = $computed((): boolean => {
-	return !props.mock && !posting && !posted &&
-		(1 <= textLength || 1 <= files.length || !!poll || !!props.renote) &&
-		(textLength <= maxTextLength) &&
-		(!poll || poll.choices.length >= 2);
+const canPost = computed((): boolean => {
+	return !props.mock && !posting.value && !posted.value &&
+		(1 <= textLength.value || 1 <= files.value.length || !!poll.value || !!props.renote) &&
+		(textLength.value <= maxTextLength.value) &&
+		(!poll.value || poll.value.choices.length >= 2);
 });
 
-const withHashtags = $computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
-const hashtags = $computed(defaultStore.makeGetterSetter('postFormHashtags'));
+const withHashtags = computed(defaultStore.makeGetterSetter('postFormWithHashtags'));
+const hashtags = computed(defaultStore.makeGetterSetter('postFormHashtags'));
 
-watch($$(text), () => {
+watch(text, () => {
 	checkMissingMention();
 }, { immediate: true });
 
-watch($$(visibility), () => {
+watch(visibility, () => {
 	checkMissingMention();
 }, { immediate: true });
 
-watch($$(visibleUsers), () => {
+watch(visibleUsers, () => {
 	checkMissingMention();
 }, {
 	deep: true,
 });
 
 if (props.mention) {
-	text = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
-	text += ' ';
+	text.value = props.mention.host ? `@${props.mention.username}@${toASCII(props.mention.host)}` : `@${props.mention.username}`;
+	text.value += ' ';
 }
 
 if (props.reply && (props.reply.user.username !== $i.username || (props.reply.user.host != null && props.reply.user.host !== host))) {
-	text = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
+	text.value = `@${props.reply.user.username}${props.reply.user.host != null ? '@' + toASCII(props.reply.user.host) : ''} `;
 }
 
 if (props.reply && props.reply.text != null) {
@@ -295,32 +295,32 @@ if (props.reply && props.reply.text != null) {
 		if ($i.username === x.username && (x.host == null || x.host === host)) continue;
 
 		// 重複は除外
-		if (text.includes(`${mention} `)) continue;
+		if (text.value.includes(`${mention} `)) continue;
 
-		text += `${mention} `;
+		text.value += `${mention} `;
 	}
 }
 
-if ($i?.isSilenced && visibility === 'public') {
-	visibility = 'home';
+if ($i?.isSilenced && visibility.value === 'public') {
+	visibility.value = 'home';
 }
 
 if (props.channel) {
-	visibility = 'public';
-	localOnly = true; // TODO: チャンネルが連合するようになった折には消す
+	visibility.value = 'public';
+	localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
 }
 
 // 公開以外へのリプライ時は元の公開範囲を引き継ぐ
 if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visibility)) {
-	if (props.reply.visibility === 'home' && visibility === 'followers') {
-		visibility = 'followers';
-	} else if (['home', 'followers'].includes(props.reply.visibility) && visibility === 'specified') {
-		visibility = 'specified';
+	if (props.reply.visibility === 'home' && visibility.value === 'followers') {
+		visibility.value = 'followers';
+	} else if (['home', 'followers'].includes(props.reply.visibility) && visibility.value === 'specified') {
+		visibility.value = 'specified';
 	} else {
-		visibility = props.reply.visibility;
+		visibility.value = props.reply.visibility;
 	}
 
-	if (visibility === 'specified') {
+	if (visibility.value === 'specified') {
 		if (props.reply.visibleUserIds) {
 			os.api('users/show', {
 				userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
@@ -338,57 +338,57 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
 }
 
 if (props.specified) {
-	visibility = 'specified';
+	visibility.value = 'specified';
 	pushVisibleUser(props.specified);
 }
 
 // keep cw when reply
 if (defaultStore.state.keepCw && props.reply && props.reply.cw) {
-	useCw = true;
-	cw = props.reply.cw;
+	useCw.value = true;
+	cw.value = props.reply.cw;
 }
 
 function watchForDraft() {
-	watch($$(text), () => saveDraft());
-	watch($$(useCw), () => saveDraft());
-	watch($$(cw), () => saveDraft());
-	watch($$(poll), () => saveDraft());
-	watch($$(files), () => saveDraft(), { deep: true });
-	watch($$(visibility), () => saveDraft());
-	watch($$(localOnly), () => saveDraft());
+	watch(text, () => saveDraft());
+	watch(useCw, () => saveDraft());
+	watch(cw, () => saveDraft());
+	watch(poll, () => saveDraft());
+	watch(files, () => saveDraft(), { deep: true });
+	watch(visibility, () => saveDraft());
+	watch(localOnly, () => saveDraft());
 }
 
 function checkMissingMention() {
-	if (visibility === 'specified') {
-		const ast = mfm.parse(text);
+	if (visibility.value === 'specified') {
+		const ast = mfm.parse(text.value);
 
 		for (const x of extractMentions(ast)) {
-			if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
-				hasNotSpecifiedMentions = true;
+			if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
+				hasNotSpecifiedMentions.value = true;
 				return;
 			}
 		}
 	}
-	hasNotSpecifiedMentions = false;
+	hasNotSpecifiedMentions.value = false;
 }
 
 function addMissingMention() {
-	const ast = mfm.parse(text);
+	const ast = mfm.parse(text.value);
 
 	for (const x of extractMentions(ast)) {
-		if (!visibleUsers.some(u => (u.username === x.username) && (u.host === x.host))) {
+		if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
 			os.api('users/show', { username: x.username, host: x.host }).then(user => {
-				visibleUsers.push(user);
+				visibleUsers.value.push(user);
 			});
 		}
 	}
 }
 
 function togglePoll() {
-	if (poll) {
-		poll = null;
+	if (poll.value) {
+		poll.value = null;
 	} else {
-		poll = {
+		poll.value = {
 			choices: ['', ''],
 			multiple: false,
 			expiresAt: null,
@@ -398,13 +398,13 @@ function togglePoll() {
 }
 
 function addTag(tag: string) {
-	insertTextAtCursor(textareaEl, ` #${tag} `);
+	insertTextAtCursor(textareaEl.value, ` #${tag} `);
 }
 
 function focus() {
-	if (textareaEl) {
-		textareaEl.focus();
-		textareaEl.setSelectionRange(textareaEl.value.length, textareaEl.value.length);
+	if (textareaEl.value) {
+		textareaEl.value.focus();
+		textareaEl.value.setSelectionRange(textareaEl.value.value.length, textareaEl.value.value.length);
 	}
 }
 
@@ -413,55 +413,55 @@ function chooseFileFrom(ev) {
 
 	selectFiles(ev.currentTarget ?? ev.target, i18n.ts.attachFile).then(files_ => {
 		for (const file of files_) {
-			files.push(file);
+			files.value.push(file);
 		}
 	});
 }
 
 function detachFile(id) {
-	files = files.filter(x => x.id !== id);
+	files.value = files.value.filter(x => x.id !== id);
 }
 
 function updateFileSensitive(file, sensitive) {
 	if (props.mock) {
 		emit('fileChangeSensitive', file.id, sensitive);
 	}
-	files[files.findIndex(x => x.id === file.id)].isSensitive = sensitive;
+	files.value[files.value.findIndex(x => x.id === file.id)].isSensitive = sensitive;
 }
 
 function updateFileName(file, name) {
-	files[files.findIndex(x => x.id === file.id)].name = name;
+	files.value[files.value.findIndex(x => x.id === file.id)].name = name;
 }
 
 function replaceFile(file: Misskey.entities.DriveFile, newFile: Misskey.entities.DriveFile): void {
-	files[files.findIndex(x => x.id === file.id)] = newFile;
+	files.value[files.value.findIndex(x => x.id === file.id)] = newFile;
 }
 
 function upload(file: File, name?: string): void {
 	if (props.mock) return;
 
 	uploadFile(file, defaultStore.state.uploadFolder, name).then(res => {
-		files.push(res);
+		files.value.push(res);
 	});
 }
 
 function setVisibility() {
 	if (props.channel) {
-		visibility = 'public';
-		localOnly = true; // TODO: チャンネルが連合するようになった折には消す
+		visibility.value = 'public';
+		localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
 		return;
 	}
 
 	os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
-		currentVisibility: visibility,
+		currentVisibility: visibility.value,
 		isSilenced: $i?.isSilenced,
-		localOnly: localOnly,
-		src: visibilityButton,
+		localOnly: localOnly.value,
+		src: visibilityButton.value,
 	}, {
 		changeVisibility: v => {
-			visibility = v;
+			visibility.value = v;
 			if (defaultStore.state.rememberNoteVisibility) {
-				defaultStore.set('visibility', visibility);
+				defaultStore.set('visibility', visibility.value);
 			}
 		},
 	}, 'closed');
@@ -469,14 +469,14 @@ function setVisibility() {
 
 async function toggleLocalOnly() {
 	if (props.channel) {
-		visibility = 'public';
-		localOnly = true; // TODO: チャンネルが連合するようになった折には消す
+		visibility.value = 'public';
+		localOnly.value = true; // TODO: チャンネルが連合するようになった折には消す
 		return;
 	}
 
 	const neverShowInfo = miLocalStorage.getItem('neverShowLocalOnlyInfo');
 
-	if (!localOnly && neverShowInfo !== 'true') {
+	if (!localOnly.value && neverShowInfo !== 'true') {
 		const confirm = await os.actions({
 			type: 'question',
 			title: i18n.ts.disableFederationConfirm,
@@ -506,7 +506,7 @@ async function toggleLocalOnly() {
 		}
 	}
 
-	localOnly = !localOnly;
+	localOnly.value = !localOnly.value;
 }
 
 async function toggleReactionAcceptance() {
@@ -519,15 +519,15 @@ async function toggleReactionAcceptance() {
 			{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote },
 			{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
 		],
-		default: reactionAcceptance,
+		default: reactionAcceptance.value,
 	});
 	if (select.canceled) return;
-	reactionAcceptance = select.result;
+	reactionAcceptance.value = select.result;
 }
 
 function pushVisibleUser(user) {
-	if (!visibleUsers.some(u => u.username === user.username && u.host === user.host)) {
-		visibleUsers.push(user);
+	if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
+		visibleUsers.value.push(user);
 	}
 }
 
@@ -535,34 +535,34 @@ function addVisibleUser() {
 	os.selectUser().then(user => {
 		pushVisibleUser(user);
 
-		if (!text.toLowerCase().includes(`@${user.username.toLowerCase()}`)) {
-			text = `@${Misskey.acct.toString(user)} ${text}`;
+		if (!text.value.toLowerCase().includes(`@${user.username.toLowerCase()}`)) {
+			text.value = `@${Misskey.acct.toString(user)} ${text.value}`;
 		}
 	});
 }
 
 function removeVisibleUser(user) {
-	visibleUsers = erase(user, visibleUsers);
+	visibleUsers.value = erase(user, visibleUsers.value);
 }
 
 function clear() {
-	text = '';
-	files = [];
-	poll = null;
-	quoteId = null;
+	text.value = '';
+	files.value = [];
+	poll.value = null;
+	quoteId.value = null;
 }
 
 function onKeydown(ev: KeyboardEvent) {
-	if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey) && canPost) post();
+	if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey) && canPost.value) post();
 	if (ev.key === 'Escape') emit('esc');
 }
 
 function onCompositionUpdate(ev: CompositionEvent) {
-	imeText = ev.data;
+	imeText.value = ev.data;
 }
 
 function onCompositionEnd(ev: CompositionEvent) {
-	imeText = '';
+	imeText.value = '';
 }
 
 async function onPaste(ev: ClipboardEvent) {
@@ -580,7 +580,7 @@ async function onPaste(ev: ClipboardEvent) {
 
 	const paste = ev.clipboardData.getData('text');
 
-	if (!props.renote && !quoteId && paste.startsWith(url + '/notes/')) {
+	if (!props.renote && !quoteId.value && paste.startsWith(url + '/notes/')) {
 		ev.preventDefault();
 
 		os.confirm({
@@ -588,11 +588,11 @@ async function onPaste(ev: ClipboardEvent) {
 			text: i18n.ts.quoteQuestion,
 		}).then(({ canceled }) => {
 			if (canceled) {
-				insertTextAtCursor(textareaEl, paste);
+				insertTextAtCursor(textareaEl.value, paste);
 				return;
 			}
 
-			quoteId = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
+			quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
 		});
 	}
 }
@@ -603,7 +603,7 @@ function onDragover(ev) {
 	const isDriveFile = ev.dataTransfer.types[0] === _DATA_TRANSFER_DRIVE_FILE_;
 	if (isFile || isDriveFile) {
 		ev.preventDefault();
-		draghover = true;
+		draghover.value = true;
 		switch (ev.dataTransfer.effectAllowed) {
 			case 'all':
 			case 'uninitialized':
@@ -624,15 +624,15 @@ function onDragover(ev) {
 }
 
 function onDragenter(ev) {
-	draghover = true;
+	draghover.value = true;
 }
 
 function onDragleave(ev) {
-	draghover = false;
+	draghover.value = false;
 }
 
 function onDrop(ev): void {
-	draghover = false;
+	draghover.value = false;
 
 	// ファイルだったら
 	if (ev.dataTransfer.files.length > 0) {
@@ -645,7 +645,7 @@ function onDrop(ev): void {
 	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
 	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
-		files.push(file);
+		files.value.push(file);
 		ev.preventDefault();
 	}
 	//#endregion
@@ -656,16 +656,16 @@ function saveDraft() {
 
 	const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
 
-	draftData[draftKey] = {
+	draftData[draftKey.value] = {
 		updatedAt: new Date(),
 		data: {
-			text: text,
-			useCw: useCw,
-			cw: cw,
-			visibility: visibility,
-			localOnly: localOnly,
-			files: files,
-			poll: poll,
+			text: text.value,
+			useCw: useCw.value,
+			cw: cw.value,
+			visibility: visibility.value,
+			localOnly: localOnly.value,
+			files: files.value,
+			poll: poll.value,
 		},
 	};
 
@@ -675,13 +675,13 @@ function saveDraft() {
 function deleteDraft() {
 	const draftData = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}');
 
-	delete draftData[draftKey];
+	delete draftData[draftKey.value];
 
 	miLocalStorage.setItem('drafts', JSON.stringify(draftData));
 }
 
 async function post(ev?: MouseEvent) {
-	if (useCw && (cw == null || cw.trim() === '')) {
+	if (useCw.value && (cw.value == null || cw.value.trim() === '')) {
 		os.alert({
 			type: 'error',
 			text: i18n.ts.cwNotationRequired,
@@ -700,13 +700,13 @@ async function post(ev?: MouseEvent) {
 	if (props.mock) return;
 
 	const annoying =
-		text.includes('$[x2') ||
-		text.includes('$[x3') ||
-		text.includes('$[x4') ||
-		text.includes('$[scale') ||
-		text.includes('$[position');
+		text.value.includes('$[x2') ||
+		text.value.includes('$[x3') ||
+		text.value.includes('$[x4') ||
+		text.value.includes('$[scale') ||
+		text.value.includes('$[position');
 
-	if (annoying && visibility === 'public') {
+	if (annoying && visibility.value === 'public') {
 		const { canceled, result } = await os.actions({
 			type: 'warning',
 			text: i18n.ts.thisPostMayBeAnnoying,
@@ -726,26 +726,26 @@ async function post(ev?: MouseEvent) {
 		if (canceled) return;
 		if (result === 'cancel') return;
 		if (result === 'home') {
-			visibility = 'home';
+			visibility.value = 'home';
 		}
 	}
 
 	let postData = {
-		text: text === '' ? null : text,
-		fileIds: files.length > 0 ? files.map(f => f.id) : undefined,
+		text: text.value === '' ? null : text.value,
+		fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
 		replyId: props.reply ? props.reply.id : undefined,
-		renoteId: props.renote ? props.renote.id : quoteId ? quoteId : undefined,
+		renoteId: props.renote ? props.renote.id : quoteId.value ? quoteId.value : undefined,
 		channelId: props.channel ? props.channel.id : undefined,
-		poll: poll,
-		cw: useCw ? cw ?? '' : null,
-		localOnly: localOnly,
-		visibility: visibility,
-		visibleUserIds: visibility === 'specified' ? visibleUsers.map(u => u.id) : undefined,
-		reactionAcceptance,
+		poll: poll.value,
+		cw: useCw.value ? cw.value ?? '' : null,
+		localOnly: localOnly.value,
+		visibility: visibility.value,
+		visibleUserIds: visibility.value === 'specified' ? visibleUsers.value.map(u => u.id) : undefined,
+		reactionAcceptance: reactionAcceptance.value,
 	};
 
-	if (withHashtags && hashtags && hashtags.trim() !== '') {
-		const hashtags_ = hashtags.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
+	if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
+		const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
 		postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
 	}
 
@@ -762,15 +762,15 @@ async function post(ev?: MouseEvent) {
 
 	let token = undefined;
 
-	if (postAccount) {
+	if (postAccount.value) {
 		const storedAccounts = await getAccounts();
-		token = storedAccounts.find(x => x.id === postAccount.id)?.token;
+		token = storedAccounts.find(x => x.id === postAccount.value.id)?.token;
 	}
 
-	posting = true;
+	posting.value = true;
 	os.api('notes/create', postData, token).then(() => {
 		if (props.freezeAfterPosted) {
-			posted = true;
+			posted.value = true;
 		} else {
 			clear();
 		}
@@ -782,8 +782,8 @@ async function post(ev?: MouseEvent) {
 				const history = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]') as string[];
 				miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
 			}
-			posting = false;
-			postAccount = null;
+			posting.value = false;
+			postAccount.value = null;
 
 			incNotesCount();
 			if (notesCount === 1) {
@@ -828,7 +828,7 @@ async function post(ev?: MouseEvent) {
 			}
 		});
 	}).catch(err => {
-		posting = false;
+		posting.value = false;
 		os.alert({
 			type: 'error',
 			text: err.message + '\n' + (err as any).id,
@@ -842,7 +842,7 @@ function cancel() {
 
 function insertMention() {
 	os.selectUser().then(user => {
-		insertTextAtCursor(textareaEl, '@' + Misskey.acct.toString(user) + ' ');
+		insertTextAtCursor(textareaEl.value, '@' + Misskey.acct.toString(user) + ' ');
 	});
 }
 
@@ -852,7 +852,7 @@ async function insertEmoji(ev: MouseEvent) {
 	emojiPicker.show(
 		ev.currentTarget ?? ev.target,
 		emoji => {
-			insertTextAtCursor(textareaEl, emoji);
+			insertTextAtCursor(textareaEl.value, emoji);
 		},
 		() => {
 			textAreaReadOnly.value = false;
@@ -866,17 +866,17 @@ function showActions(ev) {
 		text: action.title,
 		action: () => {
 			action.handler({
-				text: text,
-				cw: cw,
+				text: text.value,
+				cw: cw.value,
 			}, (key, value) => {
-				if (key === 'text') { text = value; }
-				if (key === 'cw') { useCw = value !== null; cw = value; }
+				if (key === 'text') { text.value = value; }
+				if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
 			});
 		},
 	})), ev.currentTarget ?? ev.target);
 }
 
-let postAccount = $ref<Misskey.entities.UserDetailed | null>(null);
+const postAccount = ref<Misskey.entities.UserDetailed | null>(null);
 
 function openAccountMenu(ev: MouseEvent) {
 	if (props.mock) return;
@@ -884,12 +884,12 @@ function openAccountMenu(ev: MouseEvent) {
 	openAccountMenu_({
 		withExtraOperation: false,
 		includeCurrentAccount: true,
-		active: postAccount != null ? postAccount.id : $i.id,
+		active: postAccount.value != null ? postAccount.value.id : $i.id,
 		onChoose: (account) => {
 			if (account.id === $i.id) {
-				postAccount = null;
+				postAccount.value = null;
 			} else {
-				postAccount = account;
+				postAccount.value = account;
 			}
 		},
 	}, ev);
@@ -905,23 +905,23 @@ onMounted(() => {
 	}
 
 	// TODO: detach when unmount
-	new Autocomplete(textareaEl, $$(text));
-	new Autocomplete(cwInputEl, $$(cw));
-	new Autocomplete(hashtagsInputEl, $$(hashtags));
+	new Autocomplete(textareaEl.value, text);
+	new Autocomplete(cwInputEl.value, cw);
+	new Autocomplete(hashtagsInputEl.value, hashtags);
 
 	nextTick(() => {
 		// 書きかけの投稿を復元
 		if (!props.instant && !props.mention && !props.specified && !props.mock) {
-			const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey];
+			const draft = JSON.parse(miLocalStorage.getItem('drafts') ?? '{}')[draftKey.value];
 			if (draft) {
-				text = draft.data.text;
-				useCw = draft.data.useCw;
-				cw = draft.data.cw;
-				visibility = draft.data.visibility;
-				localOnly = draft.data.localOnly;
-				files = (draft.data.files || []).filter(draftFile => draftFile);
+				text.value = draft.data.text;
+				useCw.value = draft.data.useCw;
+				cw.value = draft.data.cw;
+				visibility.value = draft.data.visibility;
+				localOnly.value = draft.data.localOnly;
+				files.value = (draft.data.files || []).filter(draftFile => draftFile);
 				if (draft.data.poll) {
-					poll = draft.data.poll;
+					poll.value = draft.data.poll;
 				}
 			}
 		}
@@ -929,21 +929,21 @@ onMounted(() => {
 		// 削除して編集
 		if (props.initialNote) {
 			const init = props.initialNote;
-			text = init.text ? init.text : '';
-			files = init.files;
-			cw = init.cw;
-			useCw = init.cw != null;
+			text.value = init.text ? init.text : '';
+			files.value = init.files;
+			cw.value = init.cw;
+			useCw.value = init.cw != null;
 			if (init.poll) {
-				poll = {
+				poll.value = {
 					choices: init.poll.choices.map(x => x.text),
 					multiple: init.poll.multiple,
 					expiresAt: init.poll.expiresAt,
 					expiredAfter: init.poll.expiredAfter,
 				};
 			}
-			visibility = init.visibility;
-			localOnly = init.localOnly;
-			quoteId = init.renote ? init.renote.id : null;
+			visibility.value = init.visibility;
+			localOnly.value = init.localOnly;
+			quoteId.value = init.renote ? init.renote.id : null;
 		}
 
 		nextTick(() => watchForDraft());
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index c07a166a83..a0fad1ab41 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModal from '@/components/MkModal.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
@@ -36,11 +36,11 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let modal = $shallowRef<InstanceType<typeof MkModal>>();
-let form = $shallowRef<InstanceType<typeof MkPostForm>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
+const form = shallowRef<InstanceType<typeof MkPostForm>>();
 
 function onPosted() {
-	modal.close({
+	modal.value.close({
 		useSendAnimation: true,
 	});
 }
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index 00c1d3808e..44555f2c13 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, watch } from 'vue';
+import { onMounted, onUnmounted, watch, ref, shallowRef } from 'vue';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { i18n } from '@/i18n.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
@@ -35,15 +35,15 @@ const RELEASE_TRANSITION_DURATION = 200;
 const PULL_BRAKE_BASE = 1.5;
 const PULL_BRAKE_FACTOR = 170;
 
-let isPullStart = $ref(false);
-let isPullEnd = $ref(false);
-let isRefreshing = $ref(false);
-let pullDistance = $ref(0);
+const isPullStart = ref(false);
+const isPullEnd = ref(false);
+const isRefreshing = ref(false);
+const pullDistance = ref(0);
 
 let supportPointerDesktop = false;
 let startScreenY: number | null = null;
 
-const rootEl = $shallowRef<HTMLDivElement>();
+const rootEl = shallowRef<HTMLDivElement>();
 let scrollEl: HTMLElement | null = null;
 
 let disabled = false;
@@ -66,17 +66,17 @@ function getScreenY(event) {
 }
 
 function moveStart(event) {
-	if (!isPullStart && !isRefreshing && !disabled) {
-		isPullStart = true;
+	if (!isPullStart.value && !isRefreshing.value && !disabled) {
+		isPullStart.value = true;
 		startScreenY = getScreenY(event);
-		pullDistance = 0;
+		pullDistance.value = 0;
 	}
 }
 
 function moveBySystem(to: number): Promise<void> {
 	return new Promise(r => {
-		const startHeight = pullDistance;
-		const overHeight = pullDistance - to;
+		const startHeight = pullDistance.value;
+		const overHeight = pullDistance.value - to;
 		if (overHeight < 1) {
 			r();
 			return;
@@ -85,36 +85,36 @@ function moveBySystem(to: number): Promise<void> {
 		let intervalId = setInterval(() => {
 			const time = Date.now() - startTime;
 			if (time > RELEASE_TRANSITION_DURATION) {
-				pullDistance = to;
+				pullDistance.value = to;
 				clearInterval(intervalId);
 				r();
 				return;
 			}
 			const nextHeight = startHeight - (overHeight / RELEASE_TRANSITION_DURATION) * time;
-			if (pullDistance < nextHeight) return;
-			pullDistance = nextHeight;
+			if (pullDistance.value < nextHeight) return;
+			pullDistance.value = nextHeight;
 		}, 1);
 	});
 }
 
 async function fixOverContent() {
-	if (pullDistance > FIRE_THRESHOLD) {
+	if (pullDistance.value > FIRE_THRESHOLD) {
 		await moveBySystem(FIRE_THRESHOLD);
 	}
 }
 
 async function closeContent() {
-	if (pullDistance > 0) {
+	if (pullDistance.value > 0) {
 		await moveBySystem(0);
 	}
 }
 
 function moveEnd() {
-	if (isPullStart && !isRefreshing) {
+	if (isPullStart.value && !isRefreshing.value) {
 		startScreenY = null;
-		if (isPullEnd) {
-			isPullEnd = false;
-			isRefreshing = true;
+		if (isPullEnd.value) {
+			isPullEnd.value = false;
+			isRefreshing.value = true;
 			fixOverContent().then(() => {
 				emit('refresh');
 				props.refresher().then(() => {
@@ -122,17 +122,17 @@ function moveEnd() {
 				});
 			});
 		} else {
-			closeContent().then(() => isPullStart = false);
+			closeContent().then(() => isPullStart.value = false);
 		}
 	}
 }
 
 function moving(event: TouchEvent | PointerEvent) {
-	if (!isPullStart || isRefreshing || disabled) return;
+	if (!isPullStart.value || isRefreshing.value || disabled) return;
 
-	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance)) {
-		pullDistance = 0;
-		isPullEnd = false;
+	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) {
+		pullDistance.value = 0;
+		isPullEnd.value = false;
 		moveEnd();
 		return;
 	}
@@ -143,13 +143,13 @@ function moving(event: TouchEvent | PointerEvent) {
 	const moveScreenY = getScreenY(event);
 
 	const moveHeight = moveScreenY - startScreenY!;
-	pullDistance = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
+	pullDistance.value = Math.min(Math.max(moveHeight, 0), MAX_PULL_DISTANCE);
 
-	if (pullDistance > 0) {
+	if (pullDistance.value > 0) {
 		if (event.cancelable) event.preventDefault();
 	}
 
-	isPullEnd = pullDistance >= FIRE_THRESHOLD;
+	isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
 }
 
 /**
@@ -159,8 +159,8 @@ function moving(event: TouchEvent | PointerEvent) {
  */
 function refreshFinished() {
 	closeContent().then(() => {
-		isPullStart = false;
-		isRefreshing = false;
+		isPullStart.value = false;
+		isRefreshing.value = false;
 	});
 }
 
@@ -182,26 +182,26 @@ function onScrollContainerScroll() {
 }
 
 function registerEventListenersForReadyToPull() {
-	if (rootEl == null) return;
-	rootEl.addEventListener('touchstart', moveStart, { passive: true });
-	rootEl.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない
+	if (rootEl.value == null) return;
+	rootEl.value.addEventListener('touchstart', moveStart, { passive: true });
+	rootEl.value.addEventListener('touchmove', moving, { passive: false }); // passive: falseにしないとpreventDefaultが使えない
 }
 
 function unregisterEventListenersForReadyToPull() {
-	if (rootEl == null) return;
-	rootEl.removeEventListener('touchstart', moveStart);
-	rootEl.removeEventListener('touchmove', moving);
+	if (rootEl.value == null) return;
+	rootEl.value.removeEventListener('touchstart', moveStart);
+	rootEl.value.removeEventListener('touchmove', moving);
 }
 
 onMounted(() => {
-	if (rootEl == null) return;
+	if (rootEl.value == null) return;
 
-	scrollEl = getScrollContainer(rootEl);
+	scrollEl = getScrollContainer(rootEl.value);
 	if (scrollEl == null) return;
 
 	scrollEl.addEventListener('scroll', onScrollContainerScroll, { passive: true });
 
-	rootEl.addEventListener('touchend', moveEnd, { passive: true });
+	rootEl.value.addEventListener('touchend', moveEnd, { passive: true });
 
 	registerEventListenersForReadyToPull();
 });
diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
index ba64775298..ebbd5e6cdc 100644
--- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue
+++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
@@ -41,6 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
+import { ref } from 'vue';
 import { $i, getAccounts } from '@/account.js';
 import MkButton from '@/components/MkButton.vue';
 import { instance } from '@/instance.js';
@@ -62,26 +63,26 @@ defineProps<{
 }>();
 
 // ServiceWorker registration
-let registration = $ref<ServiceWorkerRegistration | undefined>();
+const registration = ref<ServiceWorkerRegistration | undefined>();
 // If this browser supports push notification
-let supported = $ref(false);
+const supported = ref(false);
 // If this browser has already subscribed to push notification
-let pushSubscription = $ref<PushSubscription | null>(null);
-let pushRegistrationInServer = $ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>();
+const pushSubscription = ref<PushSubscription | null>(null);
+const pushRegistrationInServer = ref<{ state?: string; key?: string; userId: string; endpoint: string; sendReadMessage: boolean; } | undefined>();
 
 function subscribe() {
-	if (!registration || !supported || !instance.swPublickey) return;
+	if (!registration.value || !supported.value || !instance.swPublickey) return;
 
 	// SEE: https://developer.mozilla.org/en-US/docs/Web/API/PushManager/subscribe#Parameters
-	return promiseDialog(registration.pushManager.subscribe({
+	return promiseDialog(registration.value.pushManager.subscribe({
 		userVisibleOnly: true,
 		applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
 	})
 		.then(async subscription => {
-			pushSubscription = subscription;
+			pushSubscription.value = subscription;
 
 			// Register
-			pushRegistrationInServer = await api('sw/register', {
+			pushRegistrationInServer.value = await api('sw/register', {
 				endpoint: subscription.endpoint,
 				auth: encode(subscription.getKey('auth')),
 				publickey: encode(subscription.getKey('p256dh')),
@@ -102,12 +103,12 @@ function subscribe() {
 }
 
 async function unsubscribe() {
-	if (!pushSubscription) return;
+	if (!pushSubscription.value) return;
 
-	const endpoint = pushSubscription.endpoint;
+	const endpoint = pushSubscription.value.endpoint;
 	const accounts = await getAccounts();
 
-	pushRegistrationInServer = undefined;
+	pushRegistrationInServer.value = undefined;
 
 	if ($i && accounts.length >= 2) {
 		apiWithDialog('sw/unregister', {
@@ -115,11 +116,11 @@ async function unsubscribe() {
 			endpoint,
 		});
 	} else {
-		pushSubscription.unsubscribe();
+		pushSubscription.value.unsubscribe();
 		apiWithDialog('sw/unregister', {
 			endpoint,
 		});
-		pushSubscription = null;
+		pushSubscription.value = null;
 	}
 }
 
@@ -150,20 +151,20 @@ if (navigator.serviceWorker == null) {
 	// TODO: よしなに?
 } else {
 	navigator.serviceWorker.ready.then(async swr => {
-		registration = swr;
+		registration.value = swr;
 
-		pushSubscription = await registration.pushManager.getSubscription();
+		pushSubscription.value = await registration.value.pushManager.getSubscription();
 
 		if (instance.swPublickey && ('PushManager' in window) && $i && $i.token) {
-			supported = true;
+			supported.value = true;
 
-			if (pushSubscription) {
+			if (pushSubscription.value) {
 				const res = await api('sw/show-registration', {
-					endpoint: pushSubscription.endpoint,
+					endpoint: pushSubscription.value.endpoint,
 				});
 
 				if (res) {
-					pushRegistrationInServer = res;
+					pushRegistrationInServer.value = res;
 				}
 			}
 		}
@@ -171,6 +172,6 @@ if (navigator.serviceWorker == null) {
 }
 
 defineExpose({
-	pushRegistrationInServer: $$(pushRegistrationInServer),
+	pushRegistrationInServer: pushRegistrationInServer,
 });
 </script>
diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue
index c4df3e991b..2d68557aad 100644
--- a/packages/frontend/src/components/MkRadio.vue
+++ b/packages/frontend/src/components/MkRadio.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 
 const props = defineProps<{
 	modelValue: any;
@@ -36,7 +36,7 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', value: any): void;
 }>();
 
-let checked = $computed(() => props.modelValue === props.value);
+const checked = computed(() => props.modelValue === props.value);
 
 function toggle(): void {
 	if (props.disabled) return;
diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue
index 88e262d880..75eb91e7ad 100644
--- a/packages/frontend/src/components/MkReactionEffect.vue
+++ b/packages/frontend/src/components/MkReactionEffect.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as os from '@/os.js';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 
@@ -27,13 +27,13 @@ const emit = defineEmits<{
 	(ev: 'end'): void;
 }>();
 
-let up = $ref(false);
+const up = ref(false);
 const zIndex = os.claimZIndex('middle');
 const angle = (90 - (Math.random() * 180)) + 'deg';
 
 onMounted(() => {
 	window.setTimeout(() => {
-		up = true;
+		up.value = true;
 	}, 10);
 
 	window.setTimeout(() => {
diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue
index eaa7faa4f9..a14f2512f8 100644
--- a/packages/frontend/src/components/MkReactionsViewer.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { inject, watch } from 'vue';
+import { inject, watch, ref } from 'vue';
 import XReaction from '@/components/MkReactionsViewer.reaction.vue';
 import { defaultStore } from '@/store.js';
 
@@ -38,31 +38,31 @@ const emit = defineEmits<{
 
 const initialReactions = new Set(Object.keys(props.note.reactions));
 
-let reactions = $ref<[string, number][]>([]);
-let hasMoreReactions = $ref(false);
+const reactions = ref<[string, number][]>([]);
+const hasMoreReactions = ref(false);
 
-if (props.note.myReaction && !Object.keys(reactions).includes(props.note.myReaction)) {
-	reactions[props.note.myReaction] = props.note.reactions[props.note.myReaction];
+if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.myReaction)) {
+	reactions.value[props.note.myReaction] = props.note.reactions[props.note.myReaction];
 }
 
 function onMockToggleReaction(emoji: string, count: number) {
 	if (!mock) return;
 
-	const i = reactions.findIndex((item) => item[0] === emoji);
+	const i = reactions.value.findIndex((item) => item[0] === emoji);
 	if (i < 0) return;
 
-	emit('mockUpdateMyReaction', emoji, (count - reactions[i][1]));
+	emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
 }
 
 watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {
 	let newReactions: [string, number][] = [];
-	hasMoreReactions = Object.keys(newSource).length > maxNumber;
+	hasMoreReactions.value = Object.keys(newSource).length > maxNumber;
 
-	for (let i = 0; i < reactions.length; i++) {
-		const reaction = reactions[i][0];
+	for (let i = 0; i < reactions.value.length; i++) {
+		const reaction = reactions.value[i][0];
 		if (reaction in newSource && newSource[reaction] !== 0) {
-			reactions[i][1] = newSource[reaction];
-			newReactions.push(reactions[i]);
+			reactions.value[i][1] = newSource[reaction];
+			newReactions.push(reactions.value[i]);
 		}
 	}
 
@@ -80,7 +80,7 @@ watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumbe
 		newReactions.push([props.note.myReaction, newSource[props.note.myReaction]]);
 	}
 
-	reactions = newReactions;
+	reactions.value = newReactions;
 }, { immediate: true, deep: true });
 </script>
 
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 3dc9a94ae2..e69aa1be80 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, nextTick } from 'vue';
+import { onMounted, nextTick, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -23,11 +23,11 @@ import { initChart } from '@/scripts/init-chart.js';
 
 initChart();
 
-const rootEl = $shallowRef<HTMLDivElement>(null);
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
+const rootEl = shallowRef<HTMLDivElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip({
 	position: 'middle',
@@ -38,8 +38,8 @@ async function renderChart() {
 		chartInstance.destroy();
 	}
 
-	const wide = rootEl.offsetWidth > 600;
-	const narrow = rootEl.offsetWidth < 400;
+	const wide = rootEl.value.offsetWidth > 600;
+	const narrow = rootEl.value.offsetWidth < 400;
 
 	const maxDays = wide ? 10 : narrow ? 5 : 7;
 
@@ -66,7 +66,7 @@ async function renderChart() {
 		}
 	}
 
-	fetching = false;
+	fetching.value = false;
 
 	await nextTick();
 
@@ -83,7 +83,7 @@ async function renderChart() {
 
 	const marginEachCell = 12;
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'matrix',
 		data: {
 			datasets: [{
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index 0fd67a0b46..6051db1cad 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
 import { toUnicode } from 'punycode/';
 import * as Misskey from 'misskey-js';
 import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
@@ -62,17 +62,17 @@ import * as os from '@/os.js';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 
-let signing = $ref(false);
-let user = $ref<Misskey.entities.UserDetailed | null>(null);
-let username = $ref('');
-let password = $ref('');
-let token = $ref('');
-let host = $ref(toUnicode(configHost));
-let totpLogin = $ref(false);
-let queryingKey = $ref(false);
-let credentialRequest = $ref<CredentialRequestOptions | null>(null);
-let hCaptchaResponse = $ref(null);
-let reCaptchaResponse = $ref(null);
+const signing = ref(false);
+const user = ref<Misskey.entities.UserDetailed | null>(null);
+const username = ref('');
+const password = ref('');
+const token = ref('');
+const host = ref(toUnicode(configHost));
+const totpLogin = ref(false);
+const queryingKey = ref(false);
+const credentialRequest = ref<CredentialRequestOptions | null>(null);
+const hCaptchaResponse = ref(null);
+const reCaptchaResponse = ref(null);
 
 const emit = defineEmits<{
 	(ev: 'login', v: any): void;
@@ -98,11 +98,11 @@ const props = defineProps({
 
 function onUsernameChange(): void {
 	os.api('users/show', {
-		username: username,
+		username: username.value,
 	}).then(userResponse => {
-		user = userResponse;
+		user.value = userResponse;
 	}, () => {
-		user = null;
+		user.value = null;
 	});
 }
 
@@ -113,21 +113,21 @@ function onLogin(res: any): Promise<void> | void {
 }
 
 async function queryKey(): Promise<void> {
-	queryingKey = true;
-	await webAuthnRequest(credentialRequest)
+	queryingKey.value = true;
+	await webAuthnRequest(credentialRequest.value)
 		.catch(() => {
-			queryingKey = false;
+			queryingKey.value = false;
 			return Promise.reject(null);
 		}).then(credential => {
-			credentialRequest = null;
-			queryingKey = false;
-			signing = true;
+			credentialRequest.value = null;
+			queryingKey.value = false;
+			signing.value = true;
 			return os.api('signin', {
-				username,
-				password,
+				username: username.value,
+				password: password.value,
 				credential: credential.toJSON(),
-				'hcaptcha-response': hCaptchaResponse,
-				'g-recaptcha-response': reCaptchaResponse,
+				'hcaptcha-response': hCaptchaResponse.value,
+				'g-recaptcha-response': reCaptchaResponse.value,
 			});
 		}).then(res => {
 			emit('login', res);
@@ -138,39 +138,39 @@ async function queryKey(): Promise<void> {
 				type: 'error',
 				text: i18n.ts.signinFailed,
 			});
-			signing = false;
+			signing.value = false;
 		});
 }
 
 function onSubmit(): void {
-	signing = true;
-	if (!totpLogin && user && user.twoFactorEnabled) {
-		if (webAuthnSupported() && user.securityKeys) {
+	signing.value = true;
+	if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
+		if (webAuthnSupported() && user.value.securityKeys) {
 			os.api('signin', {
-				username,
-				password,
-				'hcaptcha-response': hCaptchaResponse,
-				'g-recaptcha-response': reCaptchaResponse,
+				username: username.value,
+				password: password.value,
+				'hcaptcha-response': hCaptchaResponse.value,
+				'g-recaptcha-response': reCaptchaResponse.value,
 			}).then(res => {
-				totpLogin = true;
-				signing = false;
-				credentialRequest = parseRequestOptionsFromJSON({
+				totpLogin.value = true;
+				signing.value = false;
+				credentialRequest.value = parseRequestOptionsFromJSON({
 					publicKey: res,
 				});
 			})
 				.then(() => queryKey())
 				.catch(loginFailed);
 		} else {
-			totpLogin = true;
-			signing = false;
+			totpLogin.value = true;
+			signing.value = false;
 		}
 	} else {
 		os.api('signin', {
-			username,
-			password,
-			'hcaptcha-response': hCaptchaResponse,
-			'g-recaptcha-response': reCaptchaResponse,
-			token: user?.twoFactorEnabled ? token : undefined,
+			username: username.value,
+			password: password.value,
+			'hcaptcha-response': hCaptchaResponse.value,
+			'g-recaptcha-response': reCaptchaResponse.value,
+			token: user.value?.twoFactorEnabled ? token.value : undefined,
 		}).then(res => {
 			emit('login', res);
 			onLogin(res);
@@ -218,8 +218,8 @@ function loginFailed(err: any): void {
 		}
 	}
 
-	totpLogin = false;
-	signing = false;
+	totpLogin.value = false;
+	signing.value = false;
 }
 
 function resetPassword(): void {
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index 05cef6ed3b..6f961cff05 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { shallowRef } from 'vue';
 import MkSignin from '@/components/MkSignin.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n.js';
@@ -39,15 +39,15 @@ const emit = defineEmits<{
 	(ev: 'cancelled'): void;
 }>();
 
-const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
 function onClose() {
 	emit('cancelled');
-	if (dialog) dialog.close();
+	if (dialog.value) dialog.value.close();
 }
 
 function onLogin(res) {
 	emit('done', res);
-	if (dialog) dialog.close();
+	if (dialog.value) dialog.value.close();
 }
 </script>
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index a67251eda1..08e57fd8a6 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import { toUnicode } from 'punycode/';
 import MkButton from './MkButton.vue';
 import MkInput from './MkInput.vue';
@@ -101,34 +101,34 @@ const emit = defineEmits<{
 
 const host = toUnicode(config.host);
 
-let hcaptcha = $ref<Captcha | undefined>();
-let recaptcha = $ref<Captcha | undefined>();
-let turnstile = $ref<Captcha | undefined>();
+const hcaptcha = ref<Captcha | undefined>();
+const recaptcha = ref<Captcha | undefined>();
+const 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 username = ref<string>('');
+const password = ref<string>('');
+const retypedPassword = ref<string>('');
+const invitationCode = ref<string>('');
+const email = ref('');
+const usernameState = ref<null | 'wait' | 'ok' | 'unavailable' | 'error' | 'invalid-format' | 'min-range' | 'max-range'>(null);
+const emailState = ref<null | 'wait' | 'ok' | 'unavailable:used' | 'unavailable:format' | 'unavailable:disposable' | 'unavailable:mx' | 'unavailable:smtp' | 'unavailable' | 'error'>(null);
+const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
+const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
+const submitting = ref<boolean>(false);
+const hCaptchaResponse = ref(null);
+const reCaptchaResponse = ref(null);
+const turnstileResponse = ref(null);
+const usernameAbortController = ref<null | AbortController>(null);
+const emailAbortController = ref<null | AbortController>(null);
 
-const shouldDisableSubmitting = $computed((): boolean => {
-	return submitting ||
-		instance.enableHcaptcha && !hCaptchaResponse ||
-		instance.enableRecaptcha && !reCaptchaResponse ||
-		instance.enableTurnstile && !turnstileResponse ||
-		instance.emailRequiredForSignup && emailState !== 'ok' ||
-		usernameState !== 'ok' ||
-		passwordRetypeState !== 'match';
+const shouldDisableSubmitting = computed((): boolean => {
+	return submitting.value ||
+		instance.enableHcaptcha && !hCaptchaResponse.value ||
+		instance.enableRecaptcha && !reCaptchaResponse.value ||
+		instance.enableTurnstile && !turnstileResponse.value ||
+		instance.emailRequiredForSignup && emailState.value !== 'ok' ||
+		usernameState.value !== 'ok' ||
+		passwordRetypeState.value !== 'match';
 });
 
 function getPasswordStrength(source: string): number {
@@ -156,57 +156,57 @@ function getPasswordStrength(source: string): number {
 }
 
 function onChangeUsername(): void {
-	if (username === '') {
-		usernameState = null;
+	if (username.value === '') {
+		usernameState.value = null;
 		return;
 	}
 
 	{
 		const err =
-			!username.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
-			username.length < 1 ? 'min-range' :
-			username.length > 20 ? 'max-range' :
+			!username.value.match(/^[a-zA-Z0-9_]+$/) ? 'invalid-format' :
+			username.value.length < 1 ? 'min-range' :
+			username.value.length > 20 ? 'max-range' :
 			null;
 
 		if (err) {
-			usernameState = err;
+			usernameState.value = err;
 			return;
 		}
 	}
 
-	if (usernameAbortController != null) {
-		usernameAbortController.abort();
+	if (usernameAbortController.value != null) {
+		usernameAbortController.value.abort();
 	}
-	usernameState = 'wait';
-	usernameAbortController = new AbortController();
+	usernameState.value = 'wait';
+	usernameAbortController.value = new AbortController();
 
 	os.api('username/available', {
-		username,
-	}, undefined, usernameAbortController.signal).then(result => {
-		usernameState = result.available ? 'ok' : 'unavailable';
+		username: username.value,
+	}, undefined, usernameAbortController.value.signal).then(result => {
+		usernameState.value = result.available ? 'ok' : 'unavailable';
 	}).catch((err) => {
 		if (err.name !== 'AbortError') {
-			usernameState = 'error';
+			usernameState.value = 'error';
 		}
 	});
 }
 
 function onChangeEmail(): void {
-	if (email === '') {
-		emailState = null;
+	if (email.value === '') {
+		emailState.value = null;
 		return;
 	}
 
-	if (emailAbortController != null) {
-		emailAbortController.abort();
+	if (emailAbortController.value != null) {
+		emailAbortController.value.abort();
 	}
-	emailState = 'wait';
-	emailAbortController = new AbortController();
+	emailState.value = 'wait';
+	emailAbortController.value = new AbortController();
 
 	os.api('email-address/available', {
-		emailAddress: email,
-	}, undefined, emailAbortController.signal).then(result => {
-		emailState = result.available ? 'ok' :
+		emailAddress: email.value,
+	}, undefined, emailAbortController.value.signal).then(result => {
+		emailState.value = result.available ? 'ok' :
 			result.reason === 'used' ? 'unavailable:used' :
 			result.reason === 'format' ? 'unavailable:format' :
 			result.reason === 'disposable' ? 'unavailable:disposable' :
@@ -215,55 +215,55 @@ function onChangeEmail(): void {
 			'unavailable';
 	}).catch((err) => {
 		if (err.name !== 'AbortError') {
-			emailState = 'error';
+			emailState.value = 'error';
 		}
 	});
 }
 
 function onChangePassword(): void {
-	if (password === '') {
-		passwordStrength = '';
+	if (password.value === '') {
+		passwordStrength.value = '';
 		return;
 	}
 
-	const strength = getPasswordStrength(password);
-	passwordStrength = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
+	const strength = getPasswordStrength(password.value);
+	passwordStrength.value = strength > 0.7 ? 'high' : strength > 0.3 ? 'medium' : 'low';
 }
 
 function onChangePasswordRetype(): void {
-	if (retypedPassword === '') {
-		passwordRetypeState = null;
+	if (retypedPassword.value === '') {
+		passwordRetypeState.value = null;
 		return;
 	}
 
-	passwordRetypeState = password === retypedPassword ? 'match' : 'not-match';
+	passwordRetypeState.value = password.value === retypedPassword.value ? 'match' : 'not-match';
 }
 
 async function onSubmit(): Promise<void> {
-	if (submitting) return;
-	submitting = true;
+	if (submitting.value) return;
+	submitting.value = true;
 
 	try {
 		await os.api('signup', {
-			username,
-			password,
-			emailAddress: email,
-			invitationCode,
-			'hcaptcha-response': hCaptchaResponse,
-			'g-recaptcha-response': reCaptchaResponse,
-			'turnstile-response': turnstileResponse,
+			username: username.value,
+			password: password.value,
+			emailAddress: email.value,
+			invitationCode: invitationCode.value,
+			'hcaptcha-response': hCaptchaResponse.value,
+			'g-recaptcha-response': reCaptchaResponse.value,
+			'turnstile-response': turnstileResponse.value,
 		});
 		if (instance.emailRequiredForSignup) {
 			os.alert({
 				type: 'success',
 				title: i18n.ts._signup.almostThere,
-				text: i18n.t('_signup.emailSent', { email }),
+				text: i18n.t('_signup.emailSent', { email: email.value }),
 			});
 			emit('signupEmailPending');
 		} else {
 			const res = await os.api('signin', {
-				username,
-				password,
+				username: username.value,
+				password: password.value,
 			});
 			emit('signup', res);
 
@@ -272,10 +272,10 @@ async function onSubmit(): Promise<void> {
 			}
 		}
 	} catch {
-		submitting = false;
-		hcaptcha?.reset?.();
-		recaptcha?.reset?.();
-		turnstile?.reset?.();
+		submitting.value = false;
+		hcaptcha.value?.reset?.();
+		recaptcha.value?.reset?.();
+		turnstile.value?.reset?.();
 
 		os.alert({
 			type: 'error',
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index d860ba5fe6..1467049e25 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -33,8 +33,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
-import { $ref } from 'vue/macros';
+import { shallowRef, ref } from 'vue';
+
 import XSignup from '@/components/MkSignupDialog.form.vue';
 import XServerRules from '@/components/MkSignupDialog.rules.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -52,17 +52,17 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
-const isAcceptedServerRule = $ref(false);
+const isAcceptedServerRule = ref(false);
 
 function onSignup(res) {
 	emit('done', res);
-	dialog.close();
+	dialog.value.close();
 }
 
 function onSignupEmailPending() {
-	dialog.close();
+	dialog.value.close();
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 638407872f..370894d4f4 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
@@ -44,7 +44,7 @@ const props = defineProps<{
 
 const isLong = shouldCollapsed(props.note, []);
 
-const collapsed = $ref(isLong);
+const collapsed = ref(isLong);
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index a3d82fee5e..083c34906f 100644
--- a/packages/frontend/src/components/MkTagCloud.vue
+++ b/packages/frontend/src/components/MkTagCloud.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, watch, onBeforeUnmount } from 'vue';
+import { onMounted, watch, onBeforeUnmount, ref, shallowRef } from 'vue';
 import tinycolor from 'tinycolor2';
 
 const loaded = !!window.TagCanvas;
@@ -23,13 +23,13 @@ const SAFE_FOR_HTML_ID = 'abcdefghijklmnopqrstuvwxyz';
 const computedStyle = getComputedStyle(document.documentElement);
 const idForCanvas = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
 const idForTags = Array.from({ length: 16 }, () => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
-let available = $ref(false);
-let rootEl = $shallowRef<HTMLElement | null>(null);
-let canvasEl = $shallowRef<HTMLCanvasElement | null>(null);
-let tagsEl = $shallowRef<HTMLElement | null>(null);
-let width = $ref(300);
+const available = ref(false);
+const rootEl = shallowRef<HTMLElement | null>(null);
+const canvasEl = shallowRef<HTMLCanvasElement | null>(null);
+const tagsEl = shallowRef<HTMLElement | null>(null);
+const width = ref(300);
 
-watch($$(available), () => {
+watch(available, () => {
 	try {
 		window.TagCanvas.Start(idForCanvas, idForTags, {
 			textColour: '#ffffff',
@@ -52,15 +52,15 @@ watch($$(available), () => {
 });
 
 onMounted(() => {
-	width = rootEl.offsetWidth;
+	width.value = rootEl.value.offsetWidth;
 
 	if (loaded) {
-		available = true;
+		available.value = true;
 	} else {
 		document.head.appendChild(Object.assign(document.createElement('script'), {
 			async: true,
 			src: '/client-assets/tagcanvas.min.js',
-		})).addEventListener('load', () => available = true);
+		})).addEventListener('load', () => available.value = true);
 	}
 });
 
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 04716ebab2..23afb922f3 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, onUnmounted, provide } from 'vue';
+import { computed, watch, onUnmounted, provide, ref } from 'vue';
 import { Connection } from 'misskey-js/built/streaming.js';
 import MkNotes from '@/components/MkNotes.vue';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
@@ -62,8 +62,8 @@ type TimelineQueryType = {
   roleId?: string
 }
 
-const prComponent: InstanceType<typeof MkPullToRefresh> = $ref();
-const tlComponent: InstanceType<typeof MkNotes> = $ref();
+const prComponent = ref<InstanceType<typeof MkPullToRefresh>>();
+const tlComponent = ref<InstanceType<typeof MkNotes>>();
 
 let tlNotesCount = 0;
 
@@ -74,7 +74,7 @@ const prepend = note => {
 		note._shouldInsertAd_ = true;
 	}
 
-	tlComponent.pagingComponent?.prepend(note);
+	tlComponent.value.pagingComponent?.prepend(note);
 
 	emit('note');
 
@@ -248,7 +248,7 @@ function reloadTimeline() {
 	return new Promise<void>((res) => {
 		tlNotesCount = 0;
 
-		tlComponent.pagingComponent?.reload().then(() => {
+		tlComponent.value.pagingComponent?.reload().then(() => {
 			res();
 		});
 	});
diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue
index 48908cf3e6..0446f9196a 100644
--- a/packages/frontend/src/components/MkToast.vue
+++ b/packages/frontend/src/components/MkToast.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
 
@@ -35,11 +35,11 @@ const emit = defineEmits<{
 }>();
 
 const zIndex = os.claimZIndex('high');
-let showing = $ref(true);
+const showing = ref(true);
 
 onMounted(() => {
 	window.setTimeout(() => {
-		showing = false;
+		showing.value = false;
 	}, 4000);
 });
 </script>
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index 8958accc4a..f5fa86a908 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { shallowRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkInput from './MkInput.vue';
 import MkSwitch from './MkSwitch.vue';
@@ -67,37 +67,37 @@ const emit = defineEmits<{
 	(ev: 'done', result: { name: string | null, permissions: string[] }): void;
 }>();
 
-const dialog = $shallowRef<InstanceType<typeof MkModalWindow>>();
-let name = $ref(props.initialName);
-let permissions = $ref({});
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const name = ref(props.initialName);
+const permissions = ref({});
 
 if (props.initialPermissions) {
 	for (const kind of props.initialPermissions) {
-		permissions[kind] = true;
+		permissions.value[kind] = true;
 	}
 } else {
 	for (const kind of Misskey.permissions) {
-		permissions[kind] = false;
+		permissions.value[kind] = false;
 	}
 }
 
 function ok(): void {
 	emit('done', {
-		name: name,
-		permissions: Object.keys(permissions).filter(p => permissions[p]),
+		name: name.value,
+		permissions: Object.keys(permissions.value).filter(p => permissions.value[p]),
 	});
-	dialog.close();
+	dialog.value.close();
 }
 
 function disableAll(): void {
-	for (const p in permissions) {
-		permissions[p] = false;
+	for (const p in permissions.value) {
+		permissions.value[p] = false;
 	}
 }
 
 function enableAll(): void {
-	for (const p in permissions) {
-		permissions[p] = true;
+	for (const p in permissions.value) {
+		permissions.value[p] = true;
 	}
 }
 </script>
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 2332fe9a7c..f0f1a13d0b 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -83,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, onUnmounted } from 'vue';
+import { defineAsyncComponent, onUnmounted, ref } from 'vue';
 import type { summaly } from 'summaly';
 import { url as local } from '@/config.js';
 import { i18n } from '@/i18n.js';
@@ -107,36 +107,36 @@ const props = withDefaults(defineProps<{
 });
 
 const MOBILE_THRESHOLD = 500;
-const isMobile = $ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
+const isMobile = ref(deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD);
 
 const self = props.url.startsWith(local);
 const attr = self ? 'to' : 'href';
 const target = self ? null : '_blank';
-let fetching = $ref(true);
-let title = $ref<string | null>(null);
-let description = $ref<string | null>(null);
-let thumbnail = $ref<string | null>(null);
-let icon = $ref<string | null>(null);
-let sitename = $ref<string | null>(null);
-let sensitive = $ref<boolean>(false);
-let player = $ref({
+const fetching = ref(true);
+const title = ref<string | null>(null);
+const description = ref<string | null>(null);
+const thumbnail = ref<string | null>(null);
+const icon = ref<string | null>(null);
+const sitename = ref<string | null>(null);
+const sensitive = ref<boolean>(false);
+const player = ref({
 	url: null,
 	width: null,
 	height: null,
 } as SummalyResult['player']);
-let playerEnabled = $ref(false);
-let tweetId = $ref<string | null>(null);
-let tweetExpanded = $ref(props.detail);
+const playerEnabled = ref(false);
+const tweetId = ref<string | null>(null);
+const tweetExpanded = ref(props.detail);
 const embedId = `embed${Math.random().toString().replace(/\D/, '')}`;
-let tweetHeight = $ref(150);
-let unknownUrl = $ref(false);
+const tweetHeight = ref(150);
+const unknownUrl = ref(false);
 
 const requestUrl = new URL(props.url);
 if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
 
 if (requestUrl.hostname === 'twitter.com' || requestUrl.hostname === 'mobile.twitter.com' || requestUrl.hostname === 'x.com' || requestUrl.hostname === 'mobile.x.com') {
 	const m = requestUrl.pathname.match(/^\/.+\/status(?:es)?\/(\d+)/);
-	if (m) tweetId = m[1];
+	if (m) tweetId.value = m[1];
 }
 
 if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/(?:watch|channel)')) {
@@ -148,8 +148,8 @@ requestUrl.hash = '';
 window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`)
 	.then(res => {
 		if (!res.ok) {
-			fetching = false;
-			unknownUrl = true;
+			fetching.value = false;
+			unknownUrl.value = true;
 			return;
 		}
 
@@ -157,21 +157,21 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa
 	})
 	.then((info: SummalyResult) => {
 		if (info.url == null) {
-			fetching = false;
-			unknownUrl = true;
+			fetching.value = false;
+			unknownUrl.value = true;
 			return;
 		}
 
-		fetching = false;
-		unknownUrl = false;
+		fetching.value = false;
+		unknownUrl.value = false;
 
-		title = info.title;
-		description = info.description;
-		thumbnail = info.thumbnail;
-		icon = info.icon;
-		sitename = info.sitename;
-		player = info.player;
-		sensitive = info.sensitive ?? false;
+		title.value = info.title;
+		description.value = info.description;
+		thumbnail.value = info.thumbnail;
+		icon.value = info.icon;
+		sitename.value = info.sitename;
+		player.value = info.player;
+		sensitive.value = info.sensitive ?? false;
 	});
 
 function adjustTweetHeight(message: any) {
@@ -180,7 +180,7 @@ function adjustTweetHeight(message: any) {
 	if (embed?.method !== 'twttr.private.resize') return;
 	if (embed?.id !== embedId) return;
 	const height = embed?.params[0]?.height;
-	if (height) tweetHeight = height;
+	if (height) tweetHeight.value = height;
 }
 
 const openPlayer = (): void => {
diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue
index 0ab012dfb7..81c383540c 100644
--- a/packages/frontend/src/components/MkUrlPreviewPopup.vue
+++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -28,16 +28,16 @@ const emit = defineEmits<{
 }>();
 
 const zIndex = os.claimZIndex('middle');
-let top = $ref(0);
-let left = $ref(0);
+const top = ref(0);
+const left = ref(0);
 
 onMounted(() => {
 	const rect = props.source.getBoundingClientRect();
 	const x = Math.max((rect.left + (props.source.offsetWidth / 2)) - (300 / 2), 6) + window.pageXOffset;
 	const y = rect.top + props.source.offsetHeight + window.pageYOffset;
 
-	top = y;
-	left = x;
+	top.value = y;
+	left.value = x;
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
index 235df8822f..b9fd084409 100644
--- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
+++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -66,12 +66,12 @@ const props = defineProps<{
 	announcement?: any,
 }>();
 
-let dialog = $ref(null);
-let title: string = $ref(props.announcement ? props.announcement.title : '');
-let text: string = $ref(props.announcement ? props.announcement.text : '');
-let icon: string = $ref(props.announcement ? props.announcement.icon : 'info');
-let display: string = $ref(props.announcement ? props.announcement.display : 'dialog');
-let needConfirmationToRead = $ref(props.announcement ? props.announcement.needConfirmationToRead : false);
+const dialog = ref(null);
+const title = ref<string>(props.announcement ? props.announcement.title : '');
+const text = ref<string>(props.announcement ? props.announcement.text : '');
+const icon = ref<string>(props.announcement ? props.announcement.icon : 'info');
+const display = ref<string>(props.announcement ? props.announcement.display : 'dialog');
+const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
 
 const emit = defineEmits<{
 	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
@@ -80,12 +80,12 @@ const emit = defineEmits<{
 
 async function done() {
 	const params = {
-		title: title,
-		text: text,
-		icon: icon,
+		title: title.value,
+		text: text.value,
+		icon: icon.value,
 		imageUrl: null,
-		display: display,
-		needConfirmationToRead: needConfirmationToRead,
+		display: display.value,
+		needConfirmationToRead: needConfirmationToRead.value,
 		userId: props.user.id,
 	};
 
@@ -102,7 +102,7 @@ async function done() {
 			},
 		});
 
-		dialog.close();
+		dialog.value.close();
 	} else {
 		const created = await os.apiWithDialog('admin/announcements/create', params);
 
@@ -110,14 +110,14 @@ async function done() {
 			created: created,
 		});
 
-		dialog.close();
+		dialog.value.close();
 	}
 }
 
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: title }),
+		text: i18n.t('removeAreYouSure', { x: title.value }),
 	});
 	if (canceled) return;
 
@@ -127,7 +127,7 @@ async function del() {
 		emit('done', {
 			deleted: true,
 		});
-		dialog.close();
+		dialog.value.close();
 	});
 }
 </script>
diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue
index fbc2e09b0b..75288aac02 100644
--- a/packages/frontend/src/components/MkUserCardMini.vue
+++ b/packages/frontend/src/components/MkUserCardMini.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
 import * as os from '@/os.js';
 import { acct } from '@/filters/user.js';
@@ -28,14 +28,14 @@ const props = withDefaults(defineProps<{
 	withChart: true,
 });
 
-let chartValues = $ref<number[] | null>(null);
+const chartValues = ref<number[] | null>(null);
 
 onMounted(() => {
 	if (props.withChart) {
 		os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
 			// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く
 			res.inc.splice(0, 1);
-			chartValues = res.inc;
+			chartValues.value = res.inc;
 		});
 	}
 });
diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue
index 8b792fe496..31d6fe6d04 100644
--- a/packages/frontend/src/components/MkUserOnlineIndicator.vue
+++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 
@@ -24,7 +24,7 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const text = $computed(() => {
+const text = computed(() => {
 	switch (props.user.onlineStatus) {
 		case 'online': return i18n.ts.online;
 		case 'active': return i18n.ts.active;
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index 20eb9b3e93..b703369433 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import { userPage } from '@/filters/user.js';
@@ -80,18 +80,18 @@ const emit = defineEmits<{
 }>();
 
 const zIndex = os.claimZIndex('middle');
-let user = $ref<Misskey.entities.UserDetailed | null>(null);
-let top = $ref(0);
-let left = $ref(0);
+const user = ref<Misskey.entities.UserDetailed | null>(null);
+const top = ref(0);
+const left = ref(0);
 
 function showMenu(ev: MouseEvent) {
-	const { menu, cleanup } = getUserMenu(user);
+	const { menu, cleanup } = getUserMenu(user.value);
 	os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
 }
 
 onMounted(() => {
 	if (typeof props.q === 'object') {
-		user = props.q;
+		user.value = props.q;
 	} else {
 		const query = props.q.startsWith('@') ?
 			Misskey.acct.parse(props.q.substring(1)) :
@@ -99,7 +99,7 @@ onMounted(() => {
 
 		os.api('users/show', query).then(res => {
 			if (!props.showing) return;
-			user = res;
+			user.value = res;
 		});
 	}
 
@@ -107,8 +107,8 @@ onMounted(() => {
 	const x = ((rect.left + (props.source.offsetWidth / 2)) - (300 / 2)) + window.pageXOffset;
 	const y = rect.top + props.source.offsetHeight + window.pageYOffset;
 
-	top = y;
-	left = x;
+	top.value = y;
+	left.value = x;
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index ac38c4b62f..9d41147bd2 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkInput from '@/components/MkInput.vue';
 import FormSplit from '@/components/form/split.vue';
@@ -78,43 +78,43 @@ const props = defineProps<{
 	includeSelf?: boolean;
 }>();
 
-let username = $ref('');
-let host = $ref('');
-let users: Misskey.entities.UserDetailed[] = $ref([]);
-let recentUsers: Misskey.entities.UserDetailed[] = $ref([]);
-let selected: Misskey.entities.UserDetailed | null = $ref(null);
-let dialogEl = $ref();
+const username = ref('');
+const host = ref('');
+const users = ref<Misskey.entities.UserDetailed[]>([]);
+const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
+const selected = ref<Misskey.entities.UserDetailed | null>(null);
+const dialogEl = ref();
 
 const search = () => {
-	if (username === '' && host === '') {
-		users = [];
+	if (username.value === '' && host.value === '') {
+		users.value = [];
 		return;
 	}
 	os.api('users/search-by-username-and-host', {
-		username: username,
-		host: host,
+		username: username.value,
+		host: host.value,
 		limit: 10,
 		detail: false,
 	}).then(_users => {
-		users = _users;
+		users.value = _users;
 	});
 };
 
 const ok = () => {
-	if (selected == null) return;
-	emit('ok', selected);
-	dialogEl.close();
+	if (selected.value == null) return;
+	emit('ok', selected.value);
+	dialogEl.value.close();
 
 	// 最近使ったユーザー更新
 	let recents = defaultStore.state.recentlyUsedUsers;
-	recents = recents.filter(x => x !== selected.id);
-	recents.unshift(selected.id);
+	recents = recents.filter(x => x !== selected.value.id);
+	recents.unshift(selected.value.id);
 	defaultStore.set('recentlyUsedUsers', recents.splice(0, 16));
 };
 
 const cancel = () => {
 	emit('cancel');
-	dialogEl.close();
+	dialogEl.value.close();
 };
 
 onMounted(() => {
@@ -122,9 +122,9 @@ onMounted(() => {
 		userIds: defaultStore.state.recentlyUsedUsers,
 	}).then(users => {
 		if (props.includeSelf && users.find(x => $i ? x.id === $i.id : true) == null) {
-			recentUsers = [$i, ...users];
+			recentUsers.value = [$i, ...users];
 		} else {
-			recentUsers = users;
+			recentUsers.value = users;
 		}
 	});
 });
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
index 841ab5ba0c..4bca72511d 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
@@ -53,10 +53,10 @@ import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os.js';
 import { $i } from '@/account.js';
 
-let isLocked = ref(false);
-let hideOnlineStatus = ref(false);
-let noCrawle = ref(false);
-let preventAiLearning = ref(true);
+const isLocked = ref(false);
+const hideOnlineStatus = ref(false);
+const noCrawle = ref(false);
+const preventAiLearning = ref(true);
 
 watch([isLocked, hideOnlineStatus, noCrawle, preventAiLearning], () => {
 	os.api('i/update', {
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index bbb3d3dbf5..1324ed12e1 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -42,12 +42,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick } from 'vue';
+import { nextTick, shallowRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModal from '@/components/MkModal.vue';
 import { i18n } from '@/i18n.js';
 
-const modal = $shallowRef<InstanceType<typeof MkModal>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 const props = withDefaults(defineProps<{
 	currentVisibility: typeof Misskey.noteVisibilities[number];
@@ -62,13 +62,13 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let v = $ref(props.currentVisibility);
+const v = ref(props.currentVisibility);
 
 function choose(visibility: typeof Misskey.noteVisibilities[number]): void {
-	v = visibility;
+	v.value = visibility;
 	emit('changeVisibility', visibility);
 	nextTick(() => {
-		if (modal) modal.close();
+		if (modal.value) modal.value.close();
 	});
 }
 </script>
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
index 26de7dee52..746ed3e0de 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import tinycolor from 'tinycolor2';
@@ -25,11 +25,11 @@ import { initChart } from '@/scripts/init-chart.js';
 
 initChart();
 
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
@@ -65,7 +65,7 @@ async function renderChart() {
 
 	const max = Math.max(...raw.read);
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'bar',
 		data: {
 			datasets: [{
@@ -147,7 +147,7 @@ async function renderChart() {
 		plugins: [chartVLine(vLineColor)],
 	});
 
-	fetching = false;
+	fetching.value = false;
 }
 
 onMounted(async () => {
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index 3eb5c19660..7a41720e3f 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XTimeline from './welcome.timeline.vue';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
@@ -67,15 +67,15 @@ import number from '@/filters/number.js';
 import MkNumber from '@/components/MkNumber.vue';
 import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.vue';
 
-let meta = $ref<Misskey.entities.MetaResponse | null>(null);
-let stats = $ref<Misskey.entities.StatsResponse | null>(null);
+const meta = ref<Misskey.entities.MetaResponse | null>(null);
+const stats = ref<Misskey.entities.StatsResponse | null>(null);
 
 os.api('meta', { detail: true }).then(_meta => {
-	meta = _meta;
+	meta.value = _meta;
 });
 
 os.api('stats', {}).then((res) => {
-	stats = res;
+	stats.value = res;
 });
 
 function signin() {
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index ccb8b09b6c..1150a29e03 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -53,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onBeforeUnmount, onMounted, provide } from 'vue';
+import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue';
 import contains from '@/scripts/contains.js';
 import * as os from '@/os.js';
 import { MenuItem } from '@/types/menu';
@@ -107,18 +107,18 @@ const emit = defineEmits<{
 
 provide('inWindow', true);
 
-let rootEl = $shallowRef<HTMLElement | null>();
-let showing = $ref(true);
+const rootEl = shallowRef<HTMLElement | null>();
+const showing = ref(true);
 let beforeClickedAt = 0;
-let maximized = $ref(false);
-let minimized = $ref(false);
+const maximized = ref(false);
+const minimized = ref(false);
 let unResizedTop = '';
 let unResizedLeft = '';
 let unResizedWidth = '';
 let unResizedHeight = '';
 
 function close() {
-	showing = false;
+	showing.value = false;
 }
 
 function onKeydown(evt) {
@@ -137,46 +137,46 @@ function onContextmenu(ev: MouseEvent) {
 
 // 最前面へ移動
 function top() {
-	if (rootEl) {
-		rootEl.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
+	if (rootEl.value) {
+		rootEl.value.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
 	}
 }
 
 function maximize() {
-	maximized = true;
-	unResizedTop = rootEl.style.top;
-	unResizedLeft = rootEl.style.left;
-	unResizedWidth = rootEl.style.width;
-	unResizedHeight = rootEl.style.height;
-	rootEl.style.top = '0';
-	rootEl.style.left = '0';
-	rootEl.style.width = '100%';
-	rootEl.style.height = '100%';
+	maximized.value = true;
+	unResizedTop = rootEl.value.style.top;
+	unResizedLeft = rootEl.value.style.left;
+	unResizedWidth = rootEl.value.style.width;
+	unResizedHeight = rootEl.value.style.height;
+	rootEl.value.style.top = '0';
+	rootEl.value.style.left = '0';
+	rootEl.value.style.width = '100%';
+	rootEl.value.style.height = '100%';
 }
 
 function unMaximize() {
-	maximized = false;
-	rootEl.style.top = unResizedTop;
-	rootEl.style.left = unResizedLeft;
-	rootEl.style.width = unResizedWidth;
-	rootEl.style.height = unResizedHeight;
+	maximized.value = false;
+	rootEl.value.style.top = unResizedTop;
+	rootEl.value.style.left = unResizedLeft;
+	rootEl.value.style.width = unResizedWidth;
+	rootEl.value.style.height = unResizedHeight;
 }
 
 function minimize() {
-	minimized = true;
-	unResizedWidth = rootEl.style.width;
-	unResizedHeight = rootEl.style.height;
-	rootEl.style.width = minWidth + 'px';
-	rootEl.style.height = props.mini ? '32px' : '39px';
+	minimized.value = true;
+	unResizedWidth = rootEl.value.style.width;
+	unResizedHeight = rootEl.value.style.height;
+	rootEl.value.style.width = minWidth + 'px';
+	rootEl.value.style.height = props.mini ? '32px' : '39px';
 }
 
 function unMinimize() {
-	const main = rootEl;
+	const main = rootEl.value;
 	if (main == null) return;
 
-	minimized = false;
-	rootEl.style.width = unResizedWidth;
-	rootEl.style.height = unResizedHeight;
+	minimized.value = false;
+	rootEl.value.style.width = unResizedWidth;
+	rootEl.value.style.height = unResizedHeight;
 	const browserWidth = window.innerWidth;
 	const browserHeight = window.innerHeight;
 	const windowWidth = main.offsetWidth;
@@ -192,7 +192,7 @@ function onBodyMousedown() {
 }
 
 function onDblClick() {
-	if (minimized) {
+	if (minimized.value) {
 		unMinimize();
 	} else {
 		maximize();
@@ -205,7 +205,7 @@ function onHeaderMousedown(evt: MouseEvent) {
 
 	let beforeMaximized = false;
 
-	if (maximized) {
+	if (maximized.value) {
 		beforeMaximized = true;
 		unMaximize();
 	}
@@ -219,7 +219,7 @@ function onHeaderMousedown(evt: MouseEvent) {
 
 	beforeClickedAt = Date.now();
 
-	const main = rootEl;
+	const main = rootEl.value;
 	if (main == null) return;
 
 	if (!contains(main, document.activeElement)) main.focus();
@@ -251,8 +251,8 @@ function onHeaderMousedown(evt: MouseEvent) {
 		// 右はみ出し
 		if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth;
 
-		rootEl.style.left = moveLeft + 'px';
-		rootEl.style.top = moveTop + 'px';
+		rootEl.value.style.left = moveLeft + 'px';
+		rootEl.value.style.top = moveTop + 'px';
 	}
 
 	if (beforeMaximized) {
@@ -270,7 +270,7 @@ function onHeaderMousedown(evt: MouseEvent) {
 
 // 上ハンドル掴み時
 function onTopHandleMousedown(evt) {
-	const main = rootEl;
+	const main = rootEl.value;
 	// どういうわけかnullになることがある
 	if (main == null) return;
 
@@ -298,7 +298,7 @@ function onTopHandleMousedown(evt) {
 
 // 右ハンドル掴み時
 function onRightHandleMousedown(evt) {
-	const main = rootEl;
+	const main = rootEl.value;
 	if (main == null) return;
 
 	const base = evt.clientX;
@@ -323,7 +323,7 @@ function onRightHandleMousedown(evt) {
 
 // 下ハンドル掴み時
 function onBottomHandleMousedown(evt) {
-	const main = rootEl;
+	const main = rootEl.value;
 	if (main == null) return;
 
 	const base = evt.clientY;
@@ -348,7 +348,7 @@ function onBottomHandleMousedown(evt) {
 
 // 左ハンドル掴み時
 function onLeftHandleMousedown(evt) {
-	const main = rootEl;
+	const main = rootEl.value;
 	if (main == null) return;
 
 	const base = evt.clientX;
@@ -400,27 +400,27 @@ function onBottomLeftHandleMousedown(evt) {
 // 高さを適用
 function applyTransformHeight(height) {
 	if (height > window.innerHeight) height = window.innerHeight;
-	rootEl.style.height = height + 'px';
+	rootEl.value.style.height = height + 'px';
 }
 
 // 幅を適用
 function applyTransformWidth(width) {
 	if (width > window.innerWidth) width = window.innerWidth;
-	rootEl.style.width = width + 'px';
+	rootEl.value.style.width = width + 'px';
 }
 
 // Y座標を適用
 function applyTransformTop(top) {
-	rootEl.style.top = top + 'px';
+	rootEl.value.style.top = top + 'px';
 }
 
 // X座標を適用
 function applyTransformLeft(left) {
-	rootEl.style.left = left + 'px';
+	rootEl.value.style.left = left + 'px';
 }
 
 function onBrowserResize() {
-	const main = rootEl;
+	const main = rootEl.value;
 	if (main == null) return;
 
 	const position = main.getBoundingClientRect();
@@ -438,8 +438,8 @@ onMounted(() => {
 	applyTransformWidth(props.initialWidth);
 	if (props.initialHeight) applyTransformHeight(props.initialHeight);
 
-	applyTransformTop((window.innerHeight / 2) - (rootEl.offsetHeight / 2));
-	applyTransformLeft((window.innerWidth / 2) - (rootEl.offsetWidth / 2));
+	applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2));
+	applyTransformLeft((window.innerWidth / 2) - (rootEl.value.offsetWidth / 2));
 
 	// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする
 	top();
diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue
index d74ad0eda4..c6b18aeceb 100644
--- a/packages/frontend/src/components/MkYouTubePlayer.vue
+++ b/packages/frontend/src/components/MkYouTubePlayer.vue
@@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import MkWindow from '@/components/MkWindow.vue';
 import { versatileLang } from '@/scripts/intl-const.js';
 import { defaultStore } from '@/store.js';
@@ -35,22 +36,22 @@ const props = defineProps<{
 const requestUrl = new URL(props.url);
 if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
 
-let fetching = $ref(true);
-let title = $ref<string | null>(null);
-let player = $ref({
+const fetching = ref(true);
+const title = ref<string | null>(null);
+const player = ref({
 	url: null,
 	width: null,
 	height: null,
 });
 
 const ytFetch = (): void => {
-	fetching = true;
+	fetching.value = true;
 	window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`).then(res => {
 		res.json().then(info => {
 			if (info.url == null) return;
-			title = info.title;
-			fetching = false;
-			player = info.player;
+			title.value = info.title;
+			fetching.value = false;
+			player.value = info.player;
 		});
 	});
 };
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 2c50511b8b..008d10f8eb 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { computed } from 'vue';
 import * as os from '@/os.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { url } from '@/config.js';
@@ -28,7 +29,7 @@ const props = withDefaults(defineProps<{
 
 const router = useRouter();
 
-const active = $computed(() => {
+const active = computed(() => {
 	if (props.activeClass == null) return false;
 	const resolved = router.resolve(props.to);
 	if (resolved == null) return false;
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index 421fe99127..3ef5db3fe3 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -96,7 +96,7 @@ const choseAd = (): Ad | null => {
 };
 
 const chosen = ref(choseAd());
-const shouldHide = $ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
+const shouldHide = ref(!defaultStore.state.forceShowAds && $i && $i.policies.canHideAds && (props.specify == null));
 
 function reduceFrequency(): void {
 	if (chosen.value == null) return;
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 51a454b2cc..c7e50e275a 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
 import MkA from './MkA.vue';
@@ -47,9 +47,9 @@ import { acct, userPage } from '@/filters/user.js';
 import MkUserOnlineIndicator from '@/components/MkUserOnlineIndicator.vue';
 import { defaultStore } from '@/store.js';
 
-const animation = $ref(defaultStore.state.animation);
-const squareAvatars = $ref(defaultStore.state.squareAvatars);
-const useBlurEffect = $ref(defaultStore.state.useBlurEffect);
+const animation = ref(defaultStore.state.animation);
+const squareAvatars = ref(defaultStore.state.squareAvatars);
+const useBlurEffect = ref(defaultStore.state.useBlurEffect);
 
 const props = withDefaults(defineProps<{
 	user: Misskey.entities.User;
@@ -79,11 +79,11 @@ const emit = defineEmits<{
 
 const showDecoration = props.forceShowDecoration || defaultStore.state.showAvatarDecorations;
 
-const bound = $computed(() => props.link
+const bound = computed(() => props.link
 	? { to: userPage(props.user), target: props.target }
 	: {});
 
-const url = $computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar)
+const url = computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar)
 	? getStaticImageUrl(props.user.avatarUrl)
 	: props.user.avatarUrl);
 
@@ -116,10 +116,10 @@ function getDecorationScale() {
 	return scaleX === 1 ? undefined : `${scaleX} 1`;
 }
 
-let color = $ref<string | undefined>();
+const color = ref<string | undefined>();
 
 watch(() => props.user.avatarBlurhash, () => {
-	color = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
+	color.value = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
 }, {
 	immediate: true,
 });
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 1e17bab849..a092497307 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, inject } from 'vue';
+import { computed, inject, ref } from 'vue';
 import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js';
 import { defaultStore } from '@/store.js';
 import { customEmojisMap } from '@/custom-emojis.js';
@@ -71,7 +71,7 @@ const url = computed(() => {
 });
 
 const alt = computed(() => `:${customEmojiName.value}:`);
-let errored = $ref(url.value == null);
+const errored = ref(url.value == null);
 
 function onClick(ev: MouseEvent) {
 	if (props.menu) {
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 935ca33eb5..301e691fa0 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, inject } from 'vue';
+import { onMounted, onUnmounted, ref, inject, shallowRef, computed } from 'vue';
 import tinycolor from 'tinycolor2';
 import XTabs, { Tab } from './MkPageHeader.tabs.vue';
 import { scrollToTop } from '@/scripts/scroll.js';
@@ -69,13 +69,13 @@ const metadata = injectPageMetadata();
 const hideTitle = inject('shouldOmitHeaderTitle', false);
 const thin_ = props.thin || inject('shouldHeaderThin', false);
 
-let el = $shallowRef<HTMLElement | undefined>(undefined);
+const el = shallowRef<HTMLElement | undefined>(undefined);
 const bg = ref<string | undefined>(undefined);
-let narrow = $ref(false);
-const hasTabs = $computed(() => props.tabs.length > 0);
-const hasActions = $computed(() => props.actions && props.actions.length > 0);
-const show = $computed(() => {
-	return !hideTitle || hasTabs || hasActions;
+const narrow = ref(false);
+const hasTabs = computed(() => props.tabs.length > 0);
+const hasActions = computed(() => props.actions && props.actions.length > 0);
+const show = computed(() => {
+	return !hideTitle || hasTabs.value || hasActions.value;
 });
 
 const preventDrag = (ev: TouchEvent) => {
@@ -83,8 +83,8 @@ const preventDrag = (ev: TouchEvent) => {
 };
 
 const top = () => {
-	if (el) {
-		scrollToTop(el as HTMLElement, { behavior: 'smooth' });
+	if (el.value) {
+		scrollToTop(el.value as HTMLElement, { behavior: 'smooth' });
 	}
 };
 
@@ -111,14 +111,14 @@ onMounted(() => {
 	calcBg();
 	globalEvents.on('themeChanged', calcBg);
 
-	if (el && el.parentElement) {
-		narrow = el.parentElement.offsetWidth < 500;
+	if (el.value && el.value.parentElement) {
+		narrow.value = el.value.parentElement.offsetWidth < 500;
 		ro = new ResizeObserver((entries, observer) => {
-			if (el && el.parentElement && document.body.contains(el as HTMLElement)) {
-				narrow = el.parentElement.offsetWidth < 500;
+			if (el.value && el.value.parentElement && document.body.contains(el.value as HTMLElement)) {
+				narrow.value = el.value.parentElement.offsetWidth < 500;
 			}
 		});
-		ro.observe(el.parentElement as HTMLElement);
+		ro.observe(el.value.parentElement as HTMLElement);
 	}
 });
 
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 8e9bff11d1..1d707af2d1 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -18,36 +18,36 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue';
-import { $$ } from 'vue/macros';
+import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue';
+
 import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const';
 
-const rootEl = $shallowRef<HTMLElement>();
-const headerEl = $shallowRef<HTMLElement>();
-const footerEl = $shallowRef<HTMLElement>();
-const bodyEl = $shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
+const headerEl = shallowRef<HTMLElement>();
+const footerEl = shallowRef<HTMLElement>();
+const bodyEl = shallowRef<HTMLElement>();
 
-let headerHeight = $ref<string | undefined>();
-let childStickyTop = $ref(0);
+const headerHeight = ref<string | undefined>();
+const childStickyTop = ref(0);
 const parentStickyTop = inject<Ref<number>>(CURRENT_STICKY_TOP, ref(0));
-provide(CURRENT_STICKY_TOP, $$(childStickyTop));
+provide(CURRENT_STICKY_TOP, childStickyTop);
 
-let footerHeight = $ref<string | undefined>();
-let childStickyBottom = $ref(0);
+const footerHeight = ref<string | undefined>();
+const childStickyBottom = ref(0);
 const parentStickyBottom = inject<Ref<number>>(CURRENT_STICKY_BOTTOM, ref(0));
-provide(CURRENT_STICKY_BOTTOM, $$(childStickyBottom));
+provide(CURRENT_STICKY_BOTTOM, childStickyBottom);
 
 const calc = () => {
 	// コンポーネントが表示されてないけどKeepAliveで残ってる場合などは null になる
-	if (headerEl != null) {
-		childStickyTop = parentStickyTop.value + headerEl.offsetHeight;
-		headerHeight = headerEl.offsetHeight.toString();
+	if (headerEl.value != null) {
+		childStickyTop.value = parentStickyTop.value + headerEl.value.offsetHeight;
+		headerHeight.value = headerEl.value.offsetHeight.toString();
 	}
 
 	// コンポーネントが表示されてないけどKeepAliveで残ってる場合などは null になる
-	if (footerEl != null) {
-		childStickyBottom = parentStickyBottom.value + footerEl.offsetHeight;
-		footerHeight = footerEl.offsetHeight.toString();
+	if (footerEl.value != null) {
+		childStickyBottom.value = parentStickyBottom.value + footerEl.value.offsetHeight;
+		footerHeight.value = footerEl.value.offsetHeight.toString();
 	}
 };
 
@@ -62,28 +62,28 @@ onMounted(() => {
 
 	watch([parentStickyTop, parentStickyBottom], calc);
 
-	watch($$(childStickyTop), () => {
-		bodyEl.style.setProperty('--stickyTop', `${childStickyTop}px`);
+	watch(childStickyTop, () => {
+		bodyEl.value.style.setProperty('--stickyTop', `${childStickyTop.value}px`);
 	}, {
 		immediate: true,
 	});
 
-	watch($$(childStickyBottom), () => {
-		bodyEl.style.setProperty('--stickyBottom', `${childStickyBottom}px`);
+	watch(childStickyBottom, () => {
+		bodyEl.value.style.setProperty('--stickyBottom', `${childStickyBottom.value}px`);
 	}, {
 		immediate: true,
 	});
 
-	headerEl.style.position = 'sticky';
-	headerEl.style.top = 'var(--stickyTop, 0)';
-	headerEl.style.zIndex = '1000';
+	headerEl.value.style.position = 'sticky';
+	headerEl.value.style.top = 'var(--stickyTop, 0)';
+	headerEl.value.style.zIndex = '1000';
 
-	footerEl.style.position = 'sticky';
-	footerEl.style.bottom = 'var(--stickyBottom, 0)';
-	footerEl.style.zIndex = '1000';
+	footerEl.value.style.position = 'sticky';
+	footerEl.value.style.bottom = 'var(--stickyBottom, 0)';
+	footerEl.value.style.zIndex = '1000';
 
-	observer.observe(headerEl);
-	observer.observe(footerEl);
+	observer.observe(headerEl.value);
+	observer.observe(footerEl.value);
 });
 
 onUnmounted(() => {
@@ -91,6 +91,6 @@ onUnmounted(() => {
 });
 
 defineExpose({
-	rootEl: $$(rootEl),
+	rootEl: rootEl,
 });
 </script>
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 2eeab4d284..e11db9dc31 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import isChromatic from 'chromatic/isChromatic';
-import { onMounted, onUnmounted } from 'vue';
+import { onMounted, onUnmounted, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import { dateTimeFormat } from '@/scripts/intl-const.js';
 
@@ -47,29 +47,29 @@ const invalid = Number.isNaN(_time);
 const absolute = !invalid ? dateTimeFormat.format(_time) : i18n.ts._ago.invalid;
 
 // eslint-disable-next-line vue/no-setup-props-destructure
-let now = $ref((props.origin ?? new Date()).getTime());
-const ago = $computed(() => (now - _time) / 1000/*ms*/);
+const now = ref((props.origin ?? new Date()).getTime());
+const ago = computed(() => (now.value - _time) / 1000/*ms*/);
 
-const relative = $computed<string>(() => {
+const relative = computed<string>(() => {
 	if (props.mode === 'absolute') return ''; // absoluteではrelativeを使わないので計算しない
 	if (invalid) return i18n.ts._ago.invalid;
 
 	return (
-		ago >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago / 31536000).toString() }) :
-		ago >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago / 2592000).toString() }) :
-		ago >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago / 604800).toString() }) :
-		ago >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago / 86400).toString() }) :
-		ago >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago / 3600).toString() }) :
-		ago >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago / 60)).toString() }) :
-		ago >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago % 60)).toString() }) :
-		ago >= -3 ? i18n.ts._ago.justNow :
-		ago < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago / 31536000).toString() }) :
-		ago < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago / 2592000).toString() }) :
-		ago < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago / 604800).toString() }) :
-		ago < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago / 86400).toString() }) :
-		ago < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago / 3600).toString() }) :
-		ago < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago / 60)).toString() }) :
-		i18n.t('_timeIn.seconds', { n: (~~(-ago % 60)).toString() })
+		ago.value >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago.value / 31536000).toString() }) :
+		ago.value >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago.value / 2592000).toString() }) :
+		ago.value >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago.value / 604800).toString() }) :
+		ago.value >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago.value / 86400).toString() }) :
+		ago.value >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) :
+		ago.value >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) :
+		ago.value >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) :
+		ago.value >= -3 ? i18n.ts._ago.justNow :
+		ago.value < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago.value / 31536000).toString() }) :
+		ago.value < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago.value / 2592000).toString() }) :
+		ago.value < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago.value / 604800).toString() }) :
+		ago.value < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago.value / 86400).toString() }) :
+		ago.value < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) :
+		ago.value < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) :
+		i18n.t('_timeIn.seconds', { n: (~~(-ago.value % 60)).toString() })
 	);
 });
 
@@ -77,8 +77,8 @@ let tickId: number;
 let currentInterval: number;
 
 function tick() {
-	now = (new Date()).getTime();
-	const nextInterval = ago < 60 ? 10000 : ago < 3600 ? 60000 : 180000;
+	now.value = (new Date()).getTime();
+	const nextInterval = ago.value < 60 ? 10000 : ago.value < 3600 ? 60000 : 180000;
 
 	if (currentInterval !== nextInterval) {
 		if (tickId) window.clearInterval(tickId);
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index 99f42f4fcb..9da8f8c379 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, onBeforeUnmount, provide } from 'vue';
+import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue';
 import { Resolved, Router } from '@/nirax';
 import { defaultStore } from '@/store.js';
 
@@ -46,16 +46,16 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
 }
 
 const current = resolveNested(router.current)!;
-let currentPageComponent = $shallowRef(current.route.component);
-let currentPageProps = $ref(current.props);
-let key = $ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
+const currentPageComponent = shallowRef(current.route.component);
+const currentPageProps = ref(current.props);
+const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
 
 function onChange({ resolved, key: newKey }) {
 	const current = resolveNested(resolved);
 	if (current == null) return;
-	currentPageComponent = current.route.component;
-	currentPageProps = current.props;
-	key = current.route.path + JSON.stringify(Object.fromEntries(current.props));
+	currentPageComponent.value = current.route.component;
+	currentPageProps.value = current.props;
+	key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
 }
 
 router.addListener('change', onChange);
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index 4821687ac3..72a12e3c7b 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import { version } from '@/config.js';
@@ -42,29 +42,29 @@ const props = withDefaults(defineProps<{
 }>(), {
 });
 
-let loaded = $ref(false);
-let serverIsDead = $ref(false);
-let meta = $ref<Misskey.entities.MetaResponse | null>(null);
+const loaded = ref(false);
+const serverIsDead = ref(false);
+const meta = ref<Misskey.entities.MetaResponse | null>(null);
 
 os.api('meta', {
 	detail: false,
 }).then(res => {
-	loaded = true;
-	serverIsDead = false;
-	meta = res;
+	loaded.value = true;
+	serverIsDead.value = false;
+	meta.value = res;
 	miLocalStorage.setItem('v', res.version);
 }, () => {
-	loaded = true;
-	serverIsDead = true;
+	loaded.value = true;
+	serverIsDead.value = true;
 });
 
 function reload() {
 	unisonReload();
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.error,
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index b106806135..c245b9b6cb 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -123,7 +123,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onBeforeUnmount } from 'vue';
+import { nextTick, onBeforeUnmount, ref, shallowRef, computed } from 'vue';
 import { version } from '@/config.js';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
@@ -310,18 +310,18 @@ const patrons = [
 	'SHO SEKIGUCHI',
 ];
 
-let thereIsTreasure = $ref($i && !claimedAchievements.includes('foundTreasure'));
+const thereIsTreasure = ref($i && !claimedAchievements.includes('foundTreasure'));
 
 let easterEggReady = false;
-let easterEggEmojis = $ref([]);
-let easterEggEngine = $ref(null);
-const containerEl = $shallowRef<HTMLElement>();
+const easterEggEmojis = ref([]);
+const easterEggEngine = ref(null);
+const containerEl = shallowRef<HTMLElement>();
 
 function iconLoaded() {
 	const emojis = defaultStore.state.reactions;
-	const containerWidth = containerEl.offsetWidth;
+	const containerWidth = containerEl.value.offsetWidth;
 	for (let i = 0; i < 32; i++) {
-		easterEggEmojis.push({
+		easterEggEmojis.value.push({
 			id: i.toString(),
 			top: -(128 + (Math.random() * 256)),
 			left: (Math.random() * containerWidth),
@@ -337,7 +337,7 @@ function iconLoaded() {
 function gravity() {
 	if (!easterEggReady) return;
 	easterEggReady = false;
-	easterEggEngine = physics(containerEl);
+	easterEggEngine.value = physics(containerEl.value);
 }
 
 function iLoveMisskey() {
@@ -348,19 +348,19 @@ function iLoveMisskey() {
 }
 
 function getTreasure() {
-	thereIsTreasure = false;
+	thereIsTreasure.value = false;
 	claimAchievement('foundTreasure');
 }
 
 onBeforeUnmount(() => {
-	if (easterEggEngine) {
-		easterEggEngine.stop();
+	if (easterEggEngine.value) {
+		easterEggEngine.value.stop();
 	}
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.aboutMisskey,
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index 4ae460f763..60b515be3c 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XEmoji from './emojis.emoji.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -47,44 +47,44 @@ import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 
 const customEmojiTags = getCustomEmojiTags();
-let q = $ref('');
-let searchEmojis = $ref<Misskey.entities.EmojiSimple[]>(null);
-let selectedTags = $ref(new Set());
+const q = ref('');
+const searchEmojis = ref<Misskey.entities.EmojiSimple[]>(null);
+const selectedTags = ref(new Set());
 
 function search() {
-	if ((q === '' || q == null) && selectedTags.size === 0) {
-		searchEmojis = null;
+	if ((q.value === '' || q.value == null) && selectedTags.value.size === 0) {
+		searchEmojis.value = null;
 		return;
 	}
 
-	if (selectedTags.size === 0) {
-		const queryarry = q.match(/\:([a-z0-9_]*)\:/g);
+	if (selectedTags.value.size === 0) {
+		const queryarry = q.value.match(/\:([a-z0-9_]*)\:/g);
 
 		if (queryarry) {
-			searchEmojis = customEmojis.value.filter(emoji =>
+			searchEmojis.value = customEmojis.value.filter(emoji =>
 				queryarry.includes(`:${emoji.name}:`),
 			);
 		} else {
-			searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
+			searchEmojis.value = customEmojis.value.filter(emoji => emoji.name.includes(q.value) || emoji.aliases.includes(q.value));
 		}
 	} else {
-		searchEmojis = customEmojis.value.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t)));
+		searchEmojis.value = customEmojis.value.filter(emoji => (emoji.name.includes(q.value) || emoji.aliases.includes(q.value)) && [...selectedTags.value].every(t => emoji.aliases.includes(t)));
 	}
 }
 
 function toggleTag(tag) {
-	if (selectedTags.has(tag)) {
-		selectedTags.delete(tag);
+	if (selectedTags.value.has(tag)) {
+		selectedTags.value.delete(tag);
 	} else {
-		selectedTags.add(tag);
+		selectedTags.value.add(tag);
 	}
 }
 
-watch($$(q), () => {
+watch(q, () => {
 	search();
 });
 
-watch($$(selectedTags), () => {
+watch(selectedTags, () => {
 	search();
 }, { deep: true });
 </script>
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 47fe9c4279..e01c5f7542 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
@@ -59,25 +59,25 @@ import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import FormSplit from '@/components/form/split.vue';
 import { i18n } from '@/i18n.js';
 
-let host = $ref('');
-let state = $ref('federating');
-let sort = $ref('+pubSub');
+const host = ref('');
+const state = ref('federating');
+const sort = ref('+pubSub');
 const pagination = {
 	endpoint: 'federation/instances' as const,
 	limit: 10,
 	displayLimit: 50,
 	offsetMode: true,
 	params: computed(() => ({
-		sort: sort,
-		host: host !== '' ? host : null,
+		sort: sort.value,
+		host: host.value !== '' ? host.value : null,
 		...(
-			state === 'federating' ? { federating: true } :
-			state === 'subscribing' ? { subscribing: true } :
-			state === 'publishing' ? { publishing: true } :
-			state === 'suspended' ? { suspended: true } :
-			state === 'blocked' ? { blocked: true } :
-			state === 'silenced' ? { silenced: true } :
-			state === 'notResponding' ? { notResponding: true } :
+			state.value === 'federating' ? { federating: true } :
+			state.value === 'subscribing' ? { subscribing: true } :
+			state.value === 'publishing' ? { publishing: true } :
+			state.value === 'suspended' ? { suspended: true } :
+			state.value === 'blocked' ? { blocked: true } :
+			state.value === 'silenced' ? { silenced: true } :
+			state.value === 'notResponding' ? { notResponding: true } :
 			{}),
 	})),
 } as Paging;
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index 4fa409ff4b..f463caecb6 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -102,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import XEmojis from './about.emojis.vue';
 import XFederation from './about.federation.vue';
 import { version, host } from '@/config.js';
@@ -126,23 +126,23 @@ const props = withDefaults(defineProps<{
 	initialTab: 'overview',
 });
 
-let stats = $ref(null);
-let tab = $ref(props.initialTab);
+const stats = ref(null);
+const tab = ref(props.initialTab);
 
-watch($$(tab), () => {
-	if (tab === 'charts') {
+watch(tab, () => {
+	if (tab.value === 'charts') {
 		claimAchievement('viewInstanceChart');
 	}
 });
 
 const initStats = () => os.api('stats', {
 }).then((res) => {
-	stats = res;
+	stats.value = res;
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'overview',
 	title: i18n.ts.overview,
 }, {
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 4ce0f2936c..aefff69c60 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
@@ -82,19 +82,19 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { iAmAdmin, iAmModerator } from '@/account.js';
 
-let tab = $ref('overview');
-let file: any = $ref(null);
-let info: any = $ref(null);
-let isSensitive: boolean = $ref(false);
+const tab = ref('overview');
+const file = ref<any>(null);
+const info = ref<any>(null);
+const isSensitive = ref<boolean>(false);
 
 const props = defineProps<{
 	fileId: string,
 }>();
 
 async function fetch() {
-	file = await os.api('drive/files/show', { fileId: props.fileId });
-	info = await os.api('admin/drive/show-file', { fileId: props.fileId });
-	isSensitive = file.isSensitive;
+	file.value = await os.api('drive/files/show', { fileId: props.fileId });
+	info.value = await os.api('admin/drive/show-file', { fileId: props.fileId });
+	isSensitive.value = file.value.isSensitive;
 }
 
 fetch();
@@ -102,29 +102,29 @@ fetch();
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: file.name }),
+		text: i18n.t('removeAreYouSure', { x: file.value.name }),
 	});
 	if (canceled) return;
 
 	os.apiWithDialog('drive/files/delete', {
-		fileId: file.id,
+		fileId: file.value.id,
 	});
 }
 
 async function toggleIsSensitive(v) {
 	await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
-	isSensitive = v;
+	isSensitive.value = v;
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	text: i18n.ts.openInNewTab,
 	icon: 'ti ti-external-link',
 	handler: () => {
-		window.open(file.url, '_blank');
+		window.open(file.value.url, '_blank');
 	},
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'overview',
 	title: i18n.ts.overview,
 	icon: 'ti ti-info-circle',
@@ -139,7 +139,7 @@ const headerTabs = $computed(() => [{
 }]);
 
 definePageMetadata(computed(() => ({
-	title: file ? i18n.ts.file + ': ' + file.name : i18n.ts.file,
+	title: file.value ? i18n.ts.file + ': ' + file.value.name : i18n.ts.file,
 	icon: 'ti ti-file',
 })));
 </script>
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 87ebedc296..fd839b4369 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -203,7 +203,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, watch } from 'vue';
+import { computed, defineAsyncComponent, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkChart from '@/components/MkChart.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
@@ -234,17 +234,17 @@ const props = withDefaults(defineProps<{
 	initialTab: 'overview',
 });
 
-let tab = $ref(props.initialTab);
-let chartSrc = $ref('per-user-notes');
-let user = $ref<null | Misskey.entities.UserDetailed>();
-let init = $ref<ReturnType<typeof createFetcher>>();
-let info = $ref();
-let ips = $ref(null);
-let ap = $ref(null);
-let moderator = $ref(false);
-let silenced = $ref(false);
-let suspended = $ref(false);
-let moderationNote = $ref('');
+const tab = ref(props.initialTab);
+const chartSrc = ref('per-user-notes');
+const user = ref<null | Misskey.entities.UserDetailed>();
+const init = ref<ReturnType<typeof createFetcher>>();
+const info = ref();
+const ips = ref(null);
+const ap = ref(null);
+const moderator = ref(false);
+const silenced = ref(false);
+const suspended = ref(false);
+const moderationNote = ref('');
 const filesPagination = {
 	endpoint: 'admin/drive/files' as const,
 	limit: 10,
@@ -259,7 +259,7 @@ const announcementsPagination = {
 		userId: props.userId,
 	})),
 };
-let expandedRoles = $ref([]);
+const expandedRoles = ref([]);
 
 function createFetcher() {
 	return () => Promise.all([os.api('users/show', {
@@ -269,27 +269,27 @@ function createFetcher() {
 	}), iAmAdmin ? os.api('admin/get-user-ips', {
 		userId: props.userId,
 	}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => {
-		user = _user;
-		info = _info;
-		ips = _ips;
-		moderator = info.isModerator;
-		silenced = info.isSilenced;
-		suspended = info.isSuspended;
-		moderationNote = info.moderationNote;
+		user.value = _user;
+		info.value = _info;
+		ips.value = _ips;
+		moderator.value = info.value.isModerator;
+		silenced.value = info.value.isSilenced;
+		suspended.value = info.value.isSuspended;
+		moderationNote.value = info.value.moderationNote;
 
-		watch($$(moderationNote), async () => {
-			await os.api('admin/update-user-note', { userId: user.id, text: moderationNote });
+		watch(moderationNote, async () => {
+			await os.api('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
 			await refreshUser();
 		});
 	});
 }
 
 function refreshUser() {
-	init = createFetcher();
+	init.value = createFetcher();
 }
 
 async function updateRemoteUser() {
-	await os.apiWithDialog('federation/update-remote-user', { userId: user.id });
+	await os.apiWithDialog('federation/update-remote-user', { userId: user.value.id });
 	refreshUser();
 }
 
@@ -302,7 +302,7 @@ async function resetPassword() {
 		return;
 	} else {
 		const { password } = await os.api('admin/reset-password', {
-			userId: user.id,
+			userId: user.value.id,
 		});
 		os.alert({
 			type: 'success',
@@ -317,9 +317,9 @@ async function toggleSuspend(v) {
 		text: v ? i18n.ts.suspendConfirm : i18n.ts.unsuspendConfirm,
 	});
 	if (confirm.canceled) {
-		suspended = !v;
+		suspended.value = !v;
 	} else {
-		await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: user.id });
+		await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: user.value.id });
 		await refreshUser();
 	}
 }
@@ -331,7 +331,7 @@ async function unsetUserAvatar() {
   });
   if (confirm.canceled) return;
   const process = async () => {
-    await os.api('admin/unset-user-avatar', { userId: user.id });
+    await os.api('admin/unset-user-avatar', { userId: user.value.id });
     os.success();
   };
   await process().catch(err => {
@@ -350,7 +350,7 @@ async function unsetUserBanner() {
   });
   if (confirm.canceled) return;
   const process = async () => {
-    await os.api('admin/unset-user-banner', { userId: user.id });
+    await os.api('admin/unset-user-banner', { userId: user.value.id });
     os.success();
   };
   await process().catch(err => {
@@ -369,7 +369,7 @@ async function deleteAllFiles() {
 	});
 	if (confirm.canceled) return;
 	const process = async () => {
-		await os.api('admin/delete-all-files-of-a-user', { userId: user.id });
+		await os.api('admin/delete-all-files-of-a-user', { userId: user.value.id });
 		os.success();
 	};
 	await process().catch(err => {
@@ -389,13 +389,13 @@ async function deleteAccount() {
 	if (confirm.canceled) return;
 
 	const typed = await os.inputText({
-		text: i18n.t('typeToConfirm', { x: user?.username }),
+		text: i18n.t('typeToConfirm', { x: user.value?.username }),
 	});
 	if (typed.canceled) return;
 
-	if (typed.result === user?.username) {
+	if (typed.result === user.value?.username) {
 		await os.apiWithDialog('admin/delete-account', {
-			userId: user.id,
+			userId: user.value.id,
 		});
 	} else {
 		os.alert({
@@ -438,7 +438,7 @@ async function assignRole() {
 		: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
 		: null;
 
-	await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.id, expiresAt });
+	await os.apiWithDialog('admin/roles/assign', { roleId, userId: user.value.id, expiresAt });
 	refreshUser();
 }
 
@@ -448,50 +448,50 @@ async function unassignRole(role, ev) {
 		icon: 'ti ti-x',
 		danger: true,
 		action: async () => {
-			await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.id });
+			await os.apiWithDialog('admin/roles/unassign', { roleId: role.id, userId: user.value.id });
 			refreshUser();
 		},
 	}], ev.currentTarget ?? ev.target);
 }
 
 function toggleRoleItem(role) {
-	if (expandedRoles.includes(role.id)) {
-		expandedRoles = expandedRoles.filter(x => x !== role.id);
+	if (expandedRoles.value.includes(role.id)) {
+		expandedRoles.value = expandedRoles.value.filter(x => x !== role.id);
 	} else {
-		expandedRoles.push(role.id);
+		expandedRoles.value.push(role.id);
 	}
 }
 
 function createAnnouncement() {
 	os.popup(defineAsyncComponent(() => import('@/components/MkUserAnnouncementEditDialog.vue')), {
-		user,
+		user: user.value,
 	}, {}, 'closed');
 }
 
 function editAnnouncement(announcement) {
 	os.popup(defineAsyncComponent(() => import('@/components/MkUserAnnouncementEditDialog.vue')), {
-		user,
+		user: user.value,
 		announcement,
 	}, {}, 'closed');
 }
 
 watch(() => props.userId, () => {
-	init = createFetcher();
+	init.value = createFetcher();
 }, {
 	immediate: true,
 });
 
-watch($$(user), () => {
+watch(user, () => {
 	os.api('ap/get', {
-		uri: user.uri ?? `${url}/users/${user.id}`,
+		uri: user.value.uri ?? `${url}/users/${user.value.id}`,
 	}).then(res => {
-		ap = res;
+		ap.value = res;
 	});
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'overview',
 	title: i18n.ts.overview,
 	icon: 'ti ti-info-circle',
@@ -518,7 +518,7 @@ const headerTabs = $computed(() => [{
 }]);
 
 definePageMetadata(computed(() => ({
-	title: user ? acct(user) : i18n.ts.userInfo,
+	title: user.value ? acct(user.value) : i18n.ts.userInfo,
 	icon: 'ti ti-user-exclamation',
 })));
 </script>
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index 503fb2af9c..03f28b5219 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -69,7 +69,7 @@ const metadata = injectPageMetadata();
 
 const el = shallowRef<HTMLElement>(null);
 const tabRefs = {};
-const tabHighlightEl = $shallowRef<HTMLElement | null>(null);
+const tabHighlightEl = shallowRef<HTMLElement | null>(null);
 const bg = ref(null);
 const height = ref(0);
 const hasTabs = computed(() => {
@@ -131,13 +131,13 @@ onMounted(() => {
 	watch(() => [props.tab, props.tabs], () => {
 		nextTick(() => {
 			const tabEl = tabRefs[props.tab];
-			if (tabEl && tabHighlightEl) {
+			if (tabEl && tabHighlightEl.value) {
 				// offsetWidth や offsetLeft は少数を丸めてしまうため getBoundingClientRect を使う必要がある
 				// https://developer.mozilla.org/ja/docs/Web/API/HTMLElement/offsetWidth#%E5%80%A4
 				const parentRect = tabEl.parentElement.getBoundingClientRect();
 				const rect = tabEl.getBoundingClientRect();
-				tabHighlightEl.style.width = rect.width + 'px';
-				tabHighlightEl.style.left = (rect.left - parentRect.left) + 'px';
+				tabHighlightEl.value.style.width = rect.width + 'px';
+				tabHighlightEl.value.style.left = (rect.left - parentRect.left) + 'px';
 			}
 		});
 	}, {
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 875702ee7e..3613189548 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, shallowRef, ref } from 'vue';
 
 import XHeader from './_header_.vue';
 import MkSelect from '@/components/MkSelect.vue';
@@ -61,31 +61,31 @@ import XAbuseReport from '@/components/MkAbuseReport.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let reports = $shallowRef<InstanceType<typeof MkPagination>>();
+const reports = shallowRef<InstanceType<typeof MkPagination>>();
 
-let state = $ref('unresolved');
-let reporterOrigin = $ref('combined');
-let targetUserOrigin = $ref('combined');
-let searchUsername = $ref('');
-let searchHost = $ref('');
+const state = ref('unresolved');
+const reporterOrigin = ref('combined');
+const targetUserOrigin = ref('combined');
+const searchUsername = ref('');
+const searchHost = ref('');
 
 const pagination = {
 	endpoint: 'admin/abuse-user-reports' as const,
 	limit: 10,
 	params: computed(() => ({
-		state,
-		reporterOrigin,
-		targetUserOrigin,
+		state: state.value,
+		reporterOrigin: reporterOrigin.value,
+		targetUserOrigin: targetUserOrigin.value,
 	})),
 };
 
 function resolved(reportId) {
-	reports.removeItem(reportId);
+	reports.value.removeItem(reportId);
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.abuseReports,
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 1c15e32552..f0cf786556 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -85,7 +85,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -98,7 +98,7 @@ import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let ads: any[] = $ref([]);
+const ads = ref<any[]>([]);
 
 // ISO形式はTZがUTCになってしまうので、TZ分ずらして時間を初期化
 const localTime = new Date();
@@ -109,7 +109,7 @@ let publishing: boolean | null = null;
 
 os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
 	if (adsResponse != null) {
-		ads = adsResponse.map(r => {
+		ads.value = adsResponse.map(r => {
 			const exdate = new Date(r.expiresAt);
 			const stdate = new Date(r.startsAt);
 			exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
@@ -141,7 +141,7 @@ function toggleDayOfWeek(ad, index) {
 }
 
 function add() {
-	ads.unshift({
+	ads.value.unshift({
 		id: null,
 		memo: '',
 		place: 'square',
@@ -161,7 +161,7 @@ function remove(ad) {
 		text: i18n.t('removeAreYouSure', { x: ad.url }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
-		ads = ads.filter(x => x !== ad);
+		ads.value = ads.value.filter(x => x !== ad);
 		if (ad.id == null) return;
 		os.apiWithDialog('admin/ad/delete', {
 			id: ad.id,
@@ -209,9 +209,9 @@ function save(ad) {
 }
 
 function more() {
-	os.api('admin/ad/list', { untilId: ads.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
+	os.api('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
 		if (adsResponse == null) return;
-		ads = ads.concat(adsResponse.map(r => {
+		ads.value = ads.value.concat(adsResponse.map(r => {
 			const exdate = new Date(r.expiresAt);
 			const stdate = new Date(r.startsAt);
 			exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
@@ -228,7 +228,7 @@ function more() {
 function refresh() {
 	os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
 		if (adsResponse == null) return;
-		ads = adsResponse.map(r => {
+		ads.value = adsResponse.map(r => {
 			const exdate = new Date(r.expiresAt);
 			const stdate = new Date(r.startsAt);
 			exdate.setMilliseconds(exdate.getMilliseconds() - localTimeDiff);
@@ -244,14 +244,14 @@ function refresh() {
 
 refresh();
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-plus',
 	text: i18n.ts.add,
 	handler: add,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.ads,
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index 5785fb118c..92070dc6c6 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -84,14 +84,14 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkFolder from '@/components/MkFolder.vue';
 
-let announcements: any[] = $ref([]);
+const announcements = ref<any[]>([]);
 
 os.api('admin/announcements/list').then(announcementResponse => {
-	announcements = announcementResponse;
+	announcements.value = announcementResponse;
 });
 
 function add() {
-	announcements.unshift({
+	announcements.value.unshift({
 		_id: Math.random().toString(36),
 		id: null,
 		title: 'New announcement',
@@ -111,7 +111,7 @@ function del(announcement) {
 		text: i18n.t('deleteAreYouSure', { x: announcement.title }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
-		announcements = announcements.filter(x => x !== announcement);
+		announcements.value = announcements.value.filter(x => x !== announcement);
 		os.api('admin/announcements/delete', announcement);
 	});
 }
@@ -134,27 +134,27 @@ async function save(announcement) {
 }
 
 function more() {
-	os.api('admin/announcements/list', { untilId: announcements.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id }).then(announcementResponse => {
-		announcements = announcements.concat(announcementResponse);
+	os.api('admin/announcements/list', { untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id }).then(announcementResponse => {
+		announcements.value = announcements.value.concat(announcementResponse);
 	});
 }
 
 function refresh() {
 	os.api('admin/announcements/list').then(announcementResponse => {
-		announcements = announcementResponse;
+		announcements.value = announcementResponse;
 	});
 }
 
 refresh();
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-plus',
 	text: i18n.ts.add,
 	handler: add,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.announcements,
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index 7f5709feb9..367ae38b5a 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -64,7 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, ref } from 'vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -76,37 +76,37 @@ import { i18n } from '@/i18n.js';
 
 const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'));
 
-let provider = $ref(null);
-let hcaptchaSiteKey: string | null = $ref(null);
-let hcaptchaSecretKey: string | null = $ref(null);
-let recaptchaSiteKey: string | null = $ref(null);
-let recaptchaSecretKey: string | null = $ref(null);
-let turnstileSiteKey: string | null = $ref(null);
-let turnstileSecretKey: string | null = $ref(null);
+const provider = ref(null);
+const hcaptchaSiteKey = ref<string | null>(null);
+const hcaptchaSecretKey = ref<string | null>(null);
+const recaptchaSiteKey = ref<string | null>(null);
+const recaptchaSecretKey = ref<string | null>(null);
+const turnstileSiteKey = ref<string | null>(null);
+const turnstileSecretKey = ref<string | null>(null);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	hcaptchaSiteKey = meta.hcaptchaSiteKey;
-	hcaptchaSecretKey = meta.hcaptchaSecretKey;
-	recaptchaSiteKey = meta.recaptchaSiteKey;
-	recaptchaSecretKey = meta.recaptchaSecretKey;
-	turnstileSiteKey = meta.turnstileSiteKey;
-	turnstileSecretKey = meta.turnstileSecretKey;
+	hcaptchaSiteKey.value = meta.hcaptchaSiteKey;
+	hcaptchaSecretKey.value = meta.hcaptchaSecretKey;
+	recaptchaSiteKey.value = meta.recaptchaSiteKey;
+	recaptchaSecretKey.value = meta.recaptchaSecretKey;
+	turnstileSiteKey.value = meta.turnstileSiteKey;
+	turnstileSecretKey.value = meta.turnstileSecretKey;
 
-	provider = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null;
+	provider.value = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		enableHcaptcha: provider === 'hcaptcha',
-		hcaptchaSiteKey,
-		hcaptchaSecretKey,
-		enableRecaptcha: provider === 'recaptcha',
-		recaptchaSiteKey,
-		recaptchaSecretKey,
-		enableTurnstile: provider === 'turnstile',
-		turnstileSiteKey,
-		turnstileSecretKey,
+		enableHcaptcha: provider.value === 'hcaptcha',
+		hcaptchaSiteKey: hcaptchaSiteKey.value,
+		hcaptchaSecretKey: hcaptchaSecretKey.value,
+		enableRecaptcha: provider.value === 'recaptcha',
+		recaptchaSiteKey: recaptchaSiteKey.value,
+		recaptchaSecretKey: recaptchaSecretKey.value,
+		enableTurnstile: provider.value === 'turnstile',
+		turnstileSiteKey: turnstileSiteKey.value,
+		turnstileSecretKey: turnstileSecretKey.value,
 	}).then(() => {
 		fetchInstance();
 	});
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index c6c71d46cf..28109cfd2d 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -94,7 +94,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import JSON5 from 'json5';
 import XHeader from './_header_.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -111,55 +111,55 @@ import MkButton from '@/components/MkButton.vue';
 import MkColorInput from '@/components/MkColorInput.vue';
 import { host } from '@/config.js';
 
-let iconUrl: string | null = $ref(null);
-let app192IconUrl: string | null = $ref(null);
-let app512IconUrl: string | null = $ref(null);
-let bannerUrl: string | null = $ref(null);
-let backgroundImageUrl: string | null = $ref(null);
-let themeColor: any = $ref(null);
-let defaultLightTheme: any = $ref(null);
-let defaultDarkTheme: any = $ref(null);
-let serverErrorImageUrl: string | null = $ref(null);
-let infoImageUrl: string | null = $ref(null);
-let notFoundImageUrl: string | null = $ref(null);
-let manifestJsonOverride: string = $ref('{}');
+const iconUrl = ref<string | null>(null);
+const app192IconUrl = ref<string | null>(null);
+const app512IconUrl = ref<string | null>(null);
+const bannerUrl = ref<string | null>(null);
+const backgroundImageUrl = ref<string | null>(null);
+const themeColor = ref<any>(null);
+const defaultLightTheme = ref<any>(null);
+const defaultDarkTheme = ref<any>(null);
+const serverErrorImageUrl = ref<string | null>(null);
+const infoImageUrl = ref<string | null>(null);
+const notFoundImageUrl = ref<string | null>(null);
+const manifestJsonOverride = ref<string>('{}');
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	iconUrl = meta.iconUrl;
-	app192IconUrl = meta.app192IconUrl;
-	app512IconUrl = meta.app512IconUrl;
-	bannerUrl = meta.bannerUrl;
-	backgroundImageUrl = meta.backgroundImageUrl;
-	themeColor = meta.themeColor;
-	defaultLightTheme = meta.defaultLightTheme;
-	defaultDarkTheme = meta.defaultDarkTheme;
-	serverErrorImageUrl = meta.serverErrorImageUrl;
-	infoImageUrl = meta.infoImageUrl;
-	notFoundImageUrl = meta.notFoundImageUrl;
-	manifestJsonOverride = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t');
+	iconUrl.value = meta.iconUrl;
+	app192IconUrl.value = meta.app192IconUrl;
+	app512IconUrl.value = meta.app512IconUrl;
+	bannerUrl.value = meta.bannerUrl;
+	backgroundImageUrl.value = meta.backgroundImageUrl;
+	themeColor.value = meta.themeColor;
+	defaultLightTheme.value = meta.defaultLightTheme;
+	defaultDarkTheme.value = meta.defaultDarkTheme;
+	serverErrorImageUrl.value = meta.serverErrorImageUrl;
+	infoImageUrl.value = meta.infoImageUrl;
+	notFoundImageUrl.value = meta.notFoundImageUrl;
+	manifestJsonOverride.value = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t');
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		iconUrl,
-		app192IconUrl,
-		app512IconUrl,
-		bannerUrl,
-		backgroundImageUrl,
-		themeColor: themeColor === '' ? null : themeColor,
-		defaultLightTheme: defaultLightTheme === '' ? null : defaultLightTheme,
-		defaultDarkTheme: defaultDarkTheme === '' ? null : defaultDarkTheme,
-		infoImageUrl,
-		notFoundImageUrl,
-		serverErrorImageUrl,
-		manifestJsonOverride: manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride)),
+		iconUrl: iconUrl.value,
+		app192IconUrl: app192IconUrl.value,
+		app512IconUrl: app512IconUrl.value,
+		bannerUrl: bannerUrl.value,
+		backgroundImageUrl: backgroundImageUrl.value,
+		themeColor: themeColor.value === '' ? null : themeColor.value,
+		defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value,
+		defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value,
+		infoImageUrl: infoImageUrl.value,
+		notFoundImageUrl: notFoundImageUrl.value,
+		serverErrorImageUrl: serverErrorImageUrl.value,
+		manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)),
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.branding,
diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue
index bba03deb4b..53f556bb64 100644
--- a/packages/frontend/src/pages/admin/database.vue
+++ b/packages/frontend/src/pages/admin/database.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import * as os from '@/os.js';
@@ -29,9 +29,9 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.database,
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index 32294847bb..c93a19387c 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -64,7 +64,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -78,23 +78,23 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 
-let enableEmail: boolean = $ref(false);
-let email: any = $ref(null);
-let smtpSecure: boolean = $ref(false);
-let smtpHost: string = $ref('');
-let smtpPort: number = $ref(0);
-let smtpUser: string = $ref('');
-let smtpPass: string = $ref('');
+const enableEmail = ref<boolean>(false);
+const email = ref<any>(null);
+const smtpSecure = ref<boolean>(false);
+const smtpHost = ref<string>('');
+const smtpPort = ref<number>(0);
+const smtpUser = ref<string>('');
+const smtpPass = ref<string>('');
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	enableEmail = meta.enableEmail;
-	email = meta.email;
-	smtpSecure = meta.smtpSecure;
-	smtpHost = meta.smtpHost;
-	smtpPort = meta.smtpPort;
-	smtpUser = meta.smtpUser;
-	smtpPass = meta.smtpPass;
+	enableEmail.value = meta.enableEmail;
+	email.value = meta.email;
+	smtpSecure.value = meta.smtpSecure;
+	smtpHost.value = meta.smtpHost;
+	smtpPort.value = meta.smtpPort;
+	smtpUser.value = meta.smtpUser;
+	smtpPass.value = meta.smtpPass;
 }
 
 async function testEmail() {
@@ -115,19 +115,19 @@ async function testEmail() {
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		enableEmail,
-		email,
-		smtpSecure,
-		smtpHost,
-		smtpPort,
-		smtpUser,
-		smtpPass,
+		enableEmail: enableEmail.value,
+		email: email.value,
+		smtpSecure: smtpSecure.value,
+		smtpHost: smtpHost.value,
+		smtpPort: smtpPort.value,
+		smtpUser: smtpUser.value,
+		smtpPass: smtpPass.value,
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.emailServer,
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index e614bfeb1b..22dc115fda 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -46,27 +46,27 @@ import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let deeplAuthKey: string = $ref('');
-let deeplIsPro: boolean = $ref(false);
+const deeplAuthKey = ref<string>('');
+const deeplIsPro = ref<boolean>(false);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	deeplAuthKey = meta.deeplAuthKey;
-	deeplIsPro = meta.deeplIsPro;
+	deeplAuthKey.value = meta.deeplAuthKey;
+	deeplIsPro.value = meta.deeplIsPro;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		deeplAuthKey,
-		deeplIsPro,
+		deeplAuthKey: deeplAuthKey.value,
+		deeplIsPro: deeplIsPro.value,
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.externalServices,
diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
index 41849894ea..bfe9a8c570 100644
--- a/packages/frontend/src/pages/admin/federation.vue
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
@@ -68,24 +68,24 @@ import FormSplit from '@/components/form/split.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let host = $ref('');
-let state = $ref('federating');
-let sort = $ref('+pubSub');
+const host = ref('');
+const state = ref('federating');
+const sort = ref('+pubSub');
 const pagination = {
 	endpoint: 'federation/instances' as const,
 	limit: 10,
 	offsetMode: true,
 	params: computed(() => ({
-		sort: sort,
-		host: host !== '' ? host : null,
+		sort: sort.value,
+		host: host.value !== '' ? host.value : null,
 		...(
-			state === 'federating' ? { federating: true } :
-			state === 'subscribing' ? { subscribing: true } :
-			state === 'publishing' ? { publishing: true } :
-			state === 'suspended' ? { suspended: true } :
-			state === 'blocked' ? { blocked: true } :
-			state === 'silenced' ? { silenced: true } :
-			state === 'notResponding' ? { notResponding: true } :
+			state.value === 'federating' ? { federating: true } :
+			state.value === 'subscribing' ? { subscribing: true } :
+			state.value === 'publishing' ? { publishing: true } :
+			state.value === 'suspended' ? { suspended: true } :
+			state.value === 'blocked' ? { blocked: true } :
+			state.value === 'silenced' ? { silenced: true } :
+			state.value === 'notResponding' ? { notResponding: true } :
 			{}),
 	})),
 };
@@ -98,9 +98,9 @@ function getStatus(instance) {
 	return 'Alive';
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.federation,
diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue
index 6fb6ef40f9..9d26925993 100644
--- a/packages/frontend/src/pages/admin/files.vue
+++ b/packages/frontend/src/pages/admin/files.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
@@ -45,19 +45,19 @@ import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let origin = $ref('local');
-let type = $ref(null);
-let searchHost = $ref('');
-let userId = $ref('');
-let viewMode = $ref('grid');
+const origin = ref('local');
+const type = ref(null);
+const searchHost = ref('');
+const userId = ref('');
+const viewMode = ref('grid');
 const pagination = {
 	endpoint: 'admin/drive/files' as const,
 	limit: 10,
 	params: computed(() => ({
-		type: (type && type !== '') ? type : null,
-		userId: (userId && userId !== '') ? userId : null,
-		origin: origin,
-		hostname: (searchHost && searchHost !== '') ? searchHost : null,
+		type: (type.value && type.value !== '') ? type.value : null,
+		userId: (userId.value && userId.value !== '') ? userId.value : null,
+		origin: origin.value,
+		hostname: (searchHost.value && searchHost.value !== '') ? searchHost.value : null,
 	})),
 };
 
@@ -95,7 +95,7 @@ async function find() {
 	});
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	text: i18n.ts.lookup,
 	icon: 'ti ti-search',
 	handler: find,
@@ -105,7 +105,7 @@ const headerActions = $computed(() => [{
 	handler: clear,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.files,
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index cc0cdf7466..414889125c 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onActivated, onMounted, onUnmounted, provide, watch } from 'vue';
+import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -50,32 +50,32 @@ const indexInfo = {
 
 provide('shouldOmitHeaderTitle', false);
 
-let INFO = $ref(indexInfo);
-let childInfo = $ref(null);
-let narrow = $ref(false);
-let view = $ref(null);
-let el = $ref(null);
-let pageProps = $ref({});
+const INFO = ref(indexInfo);
+const childInfo = ref(null);
+const narrow = ref(false);
+const view = ref(null);
+const el = ref(null);
+const pageProps = ref({});
 let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
 let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile;
 let noEmailServer = !instance.enableEmail;
-let thereIsUnresolvedAbuseReport = $ref(false);
-let currentPage = $computed(() => router.currentRef.value.child);
+const thereIsUnresolvedAbuseReport = ref(false);
+const currentPage = computed(() => router.currentRef.value.child);
 
 os.api('admin/abuse-user-reports', {
 	state: 'unresolved',
 	limit: 1,
 }).then(reports => {
-	if (reports.length > 0) thereIsUnresolvedAbuseReport = true;
+	if (reports.length > 0) thereIsUnresolvedAbuseReport.value = true;
 });
 
 const NARROW_THRESHOLD = 600;
 const ro = new ResizeObserver((entries, observer) => {
 	if (entries.length === 0) return;
-	narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
+	narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
 });
 
-const menuDef = $computed(() => [{
+const menuDef = computed(() => [{
 	title: i18n.ts.quickAction,
 	items: [{
 		type: 'button',
@@ -94,67 +94,67 @@ const menuDef = $computed(() => [{
 		icon: 'ti ti-dashboard',
 		text: i18n.ts.dashboard,
 		to: '/admin/overview',
-		active: currentPage?.route.name === 'overview',
+		active: currentPage.value?.route.name === 'overview',
 	}, {
 		icon: 'ti ti-users',
 		text: i18n.ts.users,
 		to: '/admin/users',
-		active: currentPage?.route.name === 'users',
+		active: currentPage.value?.route.name === 'users',
 	}, {
 		icon: 'ti ti-user-plus',
 		text: i18n.ts.invite,
 		to: '/admin/invites',
-		active: currentPage?.route.name === 'invites',
+		active: currentPage.value?.route.name === 'invites',
 	}, {
 		icon: 'ti ti-badges',
 		text: i18n.ts.roles,
 		to: '/admin/roles',
-		active: currentPage?.route.name === 'roles',
+		active: currentPage.value?.route.name === 'roles',
 	}, {
 		icon: 'ti ti-icons',
 		text: i18n.ts.customEmojis,
 		to: '/admin/emojis',
-		active: currentPage?.route.name === 'emojis',
+		active: currentPage.value?.route.name === 'emojis',
 	}, {
 		icon: 'ti ti-sparkles',
 		text: i18n.ts.avatarDecorations,
 		to: '/admin/avatar-decorations',
-		active: currentPage?.route.name === 'avatarDecorations',
+		active: currentPage.value?.route.name === 'avatarDecorations',
 	}, {
 		icon: 'ti ti-whirl',
 		text: i18n.ts.federation,
 		to: '/admin/federation',
-		active: currentPage?.route.name === 'federation',
+		active: currentPage.value?.route.name === 'federation',
 	}, {
 		icon: 'ti ti-clock-play',
 		text: i18n.ts.jobQueue,
 		to: '/admin/queue',
-		active: currentPage?.route.name === 'queue',
+		active: currentPage.value?.route.name === 'queue',
 	}, {
 		icon: 'ti ti-cloud',
 		text: i18n.ts.files,
 		to: '/admin/files',
-		active: currentPage?.route.name === 'files',
+		active: currentPage.value?.route.name === 'files',
 	}, {
 		icon: 'ti ti-speakerphone',
 		text: i18n.ts.announcements,
 		to: '/admin/announcements',
-		active: currentPage?.route.name === 'announcements',
+		active: currentPage.value?.route.name === 'announcements',
 	}, {
 		icon: 'ti ti-ad',
 		text: i18n.ts.ads,
 		to: '/admin/ads',
-		active: currentPage?.route.name === 'ads',
+		active: currentPage.value?.route.name === 'ads',
 	}, {
 		icon: 'ti ti-exclamation-circle',
 		text: i18n.ts.abuseReports,
 		to: '/admin/abuses',
-		active: currentPage?.route.name === 'abuses',
+		active: currentPage.value?.route.name === 'abuses',
 	}, {
 		icon: 'ti ti-list-search',
 		text: i18n.ts.moderationLogs,
 		to: '/admin/modlog',
-		active: currentPage?.route.name === 'modlog',
+		active: currentPage.value?.route.name === 'modlog',
 	}],
 }, {
 	title: i18n.ts.settings,
@@ -162,57 +162,57 @@ const menuDef = $computed(() => [{
 		icon: 'ti ti-settings',
 		text: i18n.ts.general,
 		to: '/admin/settings',
-		active: currentPage?.route.name === 'settings',
+		active: currentPage.value?.route.name === 'settings',
 	}, {
 		icon: 'ti ti-paint',
 		text: i18n.ts.branding,
 		to: '/admin/branding',
-		active: currentPage?.route.name === 'branding',
+		active: currentPage.value?.route.name === 'branding',
 	}, {
 		icon: 'ti ti-shield',
 		text: i18n.ts.moderation,
 		to: '/admin/moderation',
-		active: currentPage?.route.name === 'moderation',
+		active: currentPage.value?.route.name === 'moderation',
 	}, {
 		icon: 'ti ti-mail',
 		text: i18n.ts.emailServer,
 		to: '/admin/email-settings',
-		active: currentPage?.route.name === 'email-settings',
+		active: currentPage.value?.route.name === 'email-settings',
 	}, {
 		icon: 'ti ti-cloud',
 		text: i18n.ts.objectStorage,
 		to: '/admin/object-storage',
-		active: currentPage?.route.name === 'object-storage',
+		active: currentPage.value?.route.name === 'object-storage',
 	}, {
 		icon: 'ti ti-lock',
 		text: i18n.ts.security,
 		to: '/admin/security',
-		active: currentPage?.route.name === 'security',
+		active: currentPage.value?.route.name === 'security',
 	}, {
 		icon: 'ti ti-planet',
 		text: i18n.ts.relays,
 		to: '/admin/relays',
-		active: currentPage?.route.name === 'relays',
+		active: currentPage.value?.route.name === 'relays',
 	}, {
 		icon: 'ti ti-ban',
 		text: i18n.ts.instanceBlocking,
 		to: '/admin/instance-block',
-		active: currentPage?.route.name === 'instance-block',
+		active: currentPage.value?.route.name === 'instance-block',
 	}, {
 		icon: 'ti ti-ghost',
 		text: i18n.ts.proxyAccount,
 		to: '/admin/proxy-account',
-		active: currentPage?.route.name === 'proxy-account',
+		active: currentPage.value?.route.name === 'proxy-account',
 	}, {
 		icon: 'ti ti-link',
 		text: i18n.ts.externalServices,
 		to: '/admin/external-services',
-		active: currentPage?.route.name === 'external-services',
+		active: currentPage.value?.route.name === 'external-services',
 	}, {
 		icon: 'ti ti-adjustments',
 		text: i18n.ts.other,
 		to: '/admin/other-settings',
-		active: currentPage?.route.name === 'other-settings',
+		active: currentPage.value?.route.name === 'other-settings',
 	}],
 }, {
 	title: i18n.ts.info,
@@ -220,28 +220,28 @@ const menuDef = $computed(() => [{
 		icon: 'ti ti-database',
 		text: i18n.ts.database,
 		to: '/admin/database',
-		active: currentPage?.route.name === 'database',
+		active: currentPage.value?.route.name === 'database',
 	}],
 }]);
 
-watch(narrow, () => {
-	if (currentPage?.route.name == null && !narrow) {
+watch(narrow.value, () => {
+	if (currentPage.value?.route.name == null && !narrow.value) {
 		router.push('/admin/overview');
 	}
 });
 
 onMounted(() => {
-	ro.observe(el);
+	ro.observe(el.value);
 
-	narrow = el.offsetWidth < NARROW_THRESHOLD;
-	if (currentPage?.route.name == null && !narrow) {
+	narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
+	if (currentPage.value?.route.name == null && !narrow.value) {
 		router.push('/admin/overview');
 	}
 });
 
 onActivated(() => {
-	narrow = el.offsetWidth < NARROW_THRESHOLD;
-	if (currentPage?.route.name == null && !narrow) {
+	narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
+	if (currentPage.value?.route.name == null && !narrow.value) {
 		router.push('/admin/overview');
 	}
 });
@@ -251,16 +251,16 @@ onUnmounted(() => {
 });
 
 watch(router.currentRef, (to) => {
-	if (to.route.path === '/admin' && to.child?.route.name == null && !narrow) {
+	if (to.route.path === '/admin' && to.child?.route.name == null && !narrow.value) {
 		router.replace('/admin/overview');
 	}
 });
 
 provideMetadataReceiver((info) => {
 	if (info == null) {
-		childInfo = null;
+		childInfo.value = null;
 	} else {
-		childInfo = info;
+		childInfo.value = info;
 	}
 });
 
@@ -312,11 +312,11 @@ function lookup(ev: MouseEvent) {
 	}], ev.currentTarget ?? ev.target);
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(INFO);
+definePageMetadata(INFO.value);
 
 defineExpose({
 	header: {
diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue
index 259354b3d0..356eca2af6 100644
--- a/packages/frontend/src/pages/admin/instance-block.vue
+++ b/packages/frontend/src/pages/admin/instance-block.vue
@@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -32,29 +33,29 @@ import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let blockedHosts: string = $ref('');
-let silencedHosts: string = $ref('');
-let tab = $ref('block');
+const blockedHosts = ref<string>('');
+const silencedHosts = ref<string>('');
+const tab = ref('block');
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	blockedHosts = meta.blockedHosts.join('\n');
-	silencedHosts = meta.silencedHosts.join('\n');
+	blockedHosts.value = meta.blockedHosts.join('\n');
+	silencedHosts.value = meta.silencedHosts.join('\n');
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		blockedHosts: blockedHosts.split('\n') || [],
-		silencedHosts: silencedHosts.split('\n') || [],
+		blockedHosts: blockedHosts.value.split('\n') || [],
+		silencedHosts: silencedHosts.value.split('\n') || [],
 
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'block',
 	title: i18n.ts.block,
 	icon: 'ti ti-ban',
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
index 74dc2e4c36..838ef52b14 100644
--- a/packages/frontend/src/pages/admin/invites.vue
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -70,8 +70,8 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
-let type = ref('all');
-let sort = ref('+createdAt');
+const type = ref('all');
+const sort = ref('+createdAt');
 
 const pagination: Paging = {
 	endpoint: 'admin/invite/list' as const,
@@ -109,8 +109,8 @@ function deleted(id: string) {
 	}
 }
 
-const headerActions = $computed(() => []);
-const headerTabs = $computed(() => []);
+const headerActions = computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.invite,
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 47f46fe6cf..a64dad3164 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -59,7 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -74,40 +74,40 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 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 hiddenTags: string = $ref('');
-let preservedUsernames: string = $ref('');
-let tosUrl: string | null = $ref(null);
-let privacyPolicyUrl: string | null = $ref(null);
+const enableRegistration = ref<boolean>(false);
+const emailRequiredForSignup = ref<boolean>(false);
+const sensitiveWords = ref<string>('');
+const hiddenTags = ref<string>('');
+const preservedUsernames = ref<string>('');
+const tosUrl = ref<string | null>(null);
+const privacyPolicyUrl = ref<string | null>(null);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	enableRegistration = !meta.disableRegistration;
-	emailRequiredForSignup = meta.emailRequiredForSignup;
-	sensitiveWords = meta.sensitiveWords.join('\n');
-	hiddenTags = meta.hiddenTags.join('\n');
-	preservedUsernames = meta.preservedUsernames.join('\n');
-	tosUrl = meta.tosUrl;
-	privacyPolicyUrl = meta.privacyPolicyUrl;
+	enableRegistration.value = !meta.disableRegistration;
+	emailRequiredForSignup.value = meta.emailRequiredForSignup;
+	sensitiveWords.value = meta.sensitiveWords.join('\n');
+	hiddenTags.value = meta.hiddenTags.join('\n');
+	preservedUsernames.value = meta.preservedUsernames.join('\n');
+	tosUrl.value = meta.tosUrl;
+	privacyPolicyUrl.value = meta.privacyPolicyUrl;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		disableRegistration: !enableRegistration,
-		emailRequiredForSignup,
-		tosUrl,
-		privacyPolicyUrl,
-		sensitiveWords: sensitiveWords.split('\n'),
-		hiddenTags: hiddenTags.split('\n'),
-		preservedUsernames: preservedUsernames.split('\n'),
+		disableRegistration: !enableRegistration.value,
+		emailRequiredForSignup: emailRequiredForSignup.value,
+		tosUrl: tosUrl.value,
+		privacyPolicyUrl: privacyPolicyUrl.value,
+		sensitiveWords: sensitiveWords.value.split('\n'),
+		hiddenTags: hiddenTags.value.split('\n'),
+		preservedUsernames: preservedUsernames.value.split('\n'),
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.moderation,
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue
index 4b5ef5f771..7daf9acc29 100644
--- a/packages/frontend/src/pages/admin/modlog.vue
+++ b/packages/frontend/src/pages/admin/modlog.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, shallowRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XHeader from './_header_.vue';
 import XModLog from './modlog.ModLog.vue';
@@ -40,25 +40,25 @@ import MkPagination from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let logs = $shallowRef<InstanceType<typeof MkPagination>>();
+const logs = shallowRef<InstanceType<typeof MkPagination>>();
 
-let type = $ref(null);
-let moderatorId = $ref('');
+const type = ref(null);
+const moderatorId = ref('');
 
 const pagination = {
 	endpoint: 'admin/show-moderation-logs' as const,
 	limit: 30,
 	params: computed(() => ({
-		type,
-		userId: moderatorId === '' ? null : moderatorId,
+		type: type.value,
+		userId: moderatorId.value === '' ? null : moderatorId.value,
 	})),
 };
 
 console.log(Misskey);
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.moderationLogs,
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index 8d27c31068..7019971e90 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -83,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -95,58 +95,58 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 
-let useObjectStorage: boolean = $ref(false);
-let objectStorageBaseUrl: string | null = $ref(null);
-let objectStorageBucket: string | null = $ref(null);
-let objectStoragePrefix: string | null = $ref(null);
-let objectStorageEndpoint: string | null = $ref(null);
-let objectStorageRegion: string | null = $ref(null);
-let objectStoragePort: number | null = $ref(null);
-let objectStorageAccessKey: string | null = $ref(null);
-let objectStorageSecretKey: string | null = $ref(null);
-let objectStorageUseSSL: boolean = $ref(false);
-let objectStorageUseProxy: boolean = $ref(false);
-let objectStorageSetPublicRead: boolean = $ref(false);
-let objectStorageS3ForcePathStyle: boolean = $ref(true);
+const useObjectStorage = ref<boolean>(false);
+const objectStorageBaseUrl = ref<string | null>(null);
+const objectStorageBucket = ref<string | null>(null);
+const objectStoragePrefix = ref<string | null>(null);
+const objectStorageEndpoint = ref<string | null>(null);
+const objectStorageRegion = ref<string | null>(null);
+const objectStoragePort = ref<number | null>(null);
+const objectStorageAccessKey = ref<string | null>(null);
+const objectStorageSecretKey = ref<string | null>(null);
+const objectStorageUseSSL = ref<boolean>(false);
+const objectStorageUseProxy = ref<boolean>(false);
+const objectStorageSetPublicRead = ref<boolean>(false);
+const objectStorageS3ForcePathStyle = ref<boolean>(true);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	useObjectStorage = meta.useObjectStorage;
-	objectStorageBaseUrl = meta.objectStorageBaseUrl;
-	objectStorageBucket = meta.objectStorageBucket;
-	objectStoragePrefix = meta.objectStoragePrefix;
-	objectStorageEndpoint = meta.objectStorageEndpoint;
-	objectStorageRegion = meta.objectStorageRegion;
-	objectStoragePort = meta.objectStoragePort;
-	objectStorageAccessKey = meta.objectStorageAccessKey;
-	objectStorageSecretKey = meta.objectStorageSecretKey;
-	objectStorageUseSSL = meta.objectStorageUseSSL;
-	objectStorageUseProxy = meta.objectStorageUseProxy;
-	objectStorageSetPublicRead = meta.objectStorageSetPublicRead;
-	objectStorageS3ForcePathStyle = meta.objectStorageS3ForcePathStyle;
+	useObjectStorage.value = meta.useObjectStorage;
+	objectStorageBaseUrl.value = meta.objectStorageBaseUrl;
+	objectStorageBucket.value = meta.objectStorageBucket;
+	objectStoragePrefix.value = meta.objectStoragePrefix;
+	objectStorageEndpoint.value = meta.objectStorageEndpoint;
+	objectStorageRegion.value = meta.objectStorageRegion;
+	objectStoragePort.value = meta.objectStoragePort;
+	objectStorageAccessKey.value = meta.objectStorageAccessKey;
+	objectStorageSecretKey.value = meta.objectStorageSecretKey;
+	objectStorageUseSSL.value = meta.objectStorageUseSSL;
+	objectStorageUseProxy.value = meta.objectStorageUseProxy;
+	objectStorageSetPublicRead.value = meta.objectStorageSetPublicRead;
+	objectStorageS3ForcePathStyle.value = meta.objectStorageS3ForcePathStyle;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		useObjectStorage,
-		objectStorageBaseUrl,
-		objectStorageBucket,
-		objectStoragePrefix,
-		objectStorageEndpoint,
-		objectStorageRegion,
-		objectStoragePort,
-		objectStorageAccessKey,
-		objectStorageSecretKey,
-		objectStorageUseSSL,
-		objectStorageUseProxy,
-		objectStorageSetPublicRead,
-		objectStorageS3ForcePathStyle,
+		useObjectStorage: useObjectStorage.value,
+		objectStorageBaseUrl: objectStorageBaseUrl.value,
+		objectStorageBucket: objectStorageBucket.value,
+		objectStoragePrefix: objectStoragePrefix.value,
+		objectStorageEndpoint: objectStorageEndpoint.value,
+		objectStorageRegion: objectStorageRegion.value,
+		objectStoragePort: objectStoragePort.value,
+		objectStorageAccessKey: objectStorageAccessKey.value,
+		objectStorageSecretKey: objectStorageSecretKey.value,
+		objectStorageUseSSL: objectStorageUseSSL.value,
+		objectStorageUseProxy: objectStorageUseProxy.value,
+		objectStorageSetPublicRead: objectStorageSetPublicRead.value,
+		objectStorageS3ForcePathStyle: objectStorageS3ForcePathStyle.value,
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.objectStorage,
diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue
index 7574c9d7d9..5bb328ac92 100644
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ b/packages/frontend/src/pages/admin/other-settings.vue
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
@@ -52,38 +52,38 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkSwitch from '@/components/MkSwitch.vue';
 
-let enableServerMachineStats: boolean = $ref(false);
-let enableIdenticonGeneration: boolean = $ref(false);
-let enableChartsForRemoteUser: boolean = $ref(false);
-let enableChartsForFederatedInstances: boolean = $ref(false);
+const enableServerMachineStats = ref<boolean>(false);
+const enableIdenticonGeneration = ref<boolean>(false);
+const enableChartsForRemoteUser = ref<boolean>(false);
+const enableChartsForFederatedInstances = ref<boolean>(false);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	enableServerMachineStats = meta.enableServerMachineStats;
-	enableIdenticonGeneration = meta.enableIdenticonGeneration;
-	enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
-	enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
+	enableServerMachineStats.value = meta.enableServerMachineStats;
+	enableIdenticonGeneration.value = meta.enableIdenticonGeneration;
+	enableChartsForRemoteUser.value = meta.enableChartsForRemoteUser;
+	enableChartsForFederatedInstances.value = meta.enableChartsForFederatedInstances;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		enableServerMachineStats,
-		enableIdenticonGeneration,
-		enableChartsForRemoteUser,
-		enableChartsForFederatedInstances,
+		enableServerMachineStats: enableServerMachineStats.value,
+		enableIdenticonGeneration: enableIdenticonGeneration.value,
+		enableChartsForRemoteUser: enableChartsForRemoteUser.value,
+		enableChartsForFederatedInstances: enableChartsForFederatedInstances.value,
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-check',
 	text: i18n.ts.save,
 	handler: save,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.other,
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index 8426c463d2..5e67370c2b 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import * as os from '@/os.js';
@@ -24,11 +24,11 @@ import { initChart } from '@/scripts/init-chart.js';
 
 initChart();
 
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 7;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
@@ -61,7 +61,7 @@ async function renderChart() {
 
 	const max = Math.max(...raw.read);
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'bar',
 		data: {
 			datasets: [{
@@ -155,7 +155,7 @@ async function renderChart() {
 		plugins: [chartVLine(vLineColor)],
 	});
 
-	fetching = false;
+	fetching.value = false;
 }
 
 onMounted(async () => {
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index cd54041c34..0de62fadea 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import * as os from '@/os.js';
@@ -33,9 +33,9 @@ import { initChart } from '@/scripts/init-chart.js';
 initChart();
 
 const chartLimit = 50;
-const chartEl = $shallowRef<HTMLCanvasElement>();
-const chartEl2 = $shallowRef<HTMLCanvasElement>();
-let fetching = $ref(true);
+const chartEl = shallowRef<HTMLCanvasElement>();
+const chartEl2 = shallowRef<HTMLCanvasElement>();
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 const { handler: externalTooltipHandler2 } = useChartTooltip();
@@ -74,7 +74,7 @@ onMounted(async () => {
 	const succMax = Math.max(...raw.deliverSucceeded);
 	const failMax = Math.max(...raw.deliverFailed);
 
-	new Chart(chartEl, {
+	new Chart(chartEl.value, {
 		type: 'line',
 		data: {
 			datasets: [{
@@ -178,7 +178,7 @@ onMounted(async () => {
 		plugins: [chartVLine(vLineColor)],
 	});
 
-	new Chart(chartEl2, {
+	new Chart(chartEl2.value, {
 		type: 'bar',
 		data: {
 			datasets: [{
@@ -265,7 +265,7 @@ onMounted(async () => {
 		plugins: [chartVLine(vLineColor)],
 	});
 
-	fetching = false;
+	fetching.value = false;
 });
 </script>
 
diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue
index 346a9c0258..033fc9ad85 100644
--- a/packages/frontend/src/pages/admin/overview.federation.vue
+++ b/packages/frontend/src/pages/admin/overview.federation.vue
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import XPie from './overview.pie.vue';
 import * as os from '@/os.js';
 import number from '@/filters/number.js';
@@ -54,25 +54,25 @@ import MkNumberDiff from '@/components/MkNumberDiff.vue';
 import { i18n } from '@/i18n.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 
-let topSubInstancesForPie: any = $ref(null);
-let topPubInstancesForPie: any = $ref(null);
-let federationPubActive = $ref<number | null>(null);
-let federationPubActiveDiff = $ref<number | null>(null);
-let federationSubActive = $ref<number | null>(null);
-let federationSubActiveDiff = $ref<number | null>(null);
-let fetching = $ref(true);
+const topSubInstancesForPie = ref<any>(null);
+const topPubInstancesForPie = ref<any>(null);
+const federationPubActive = ref<number | null>(null);
+const federationPubActiveDiff = ref<number | null>(null);
+const federationSubActive = ref<number | null>(null);
+const federationSubActiveDiff = ref<number | null>(null);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
 onMounted(async () => {
 	const chart = await os.apiGet('charts/federation', { limit: 2, span: 'day' });
-	federationPubActive = chart.pubActive[0];
-	federationPubActiveDiff = chart.pubActive[0] - chart.pubActive[1];
-	federationSubActive = chart.subActive[0];
-	federationSubActiveDiff = chart.subActive[0] - chart.subActive[1];
+	federationPubActive.value = chart.pubActive[0];
+	federationPubActiveDiff.value = chart.pubActive[0] - chart.pubActive[1];
+	federationSubActive.value = chart.subActive[0];
+	federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
 
 	os.apiGet('federation/stats', { limit: 10 }).then(res => {
-		topSubInstancesForPie = res.topSubInstances.map(x => ({
+		topSubInstancesForPie.value = res.topSubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followersCount,
@@ -80,7 +80,7 @@ onMounted(async () => {
 				os.pageWindow(`/instance-info/${x.host}`);
 			},
 		})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]);
-		topPubInstancesForPie = res.topPubInstances.map(x => ({
+		topPubInstancesForPie.value = res.topPubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followingCount,
@@ -90,7 +90,7 @@ onMounted(async () => {
 		})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowingCount }]);
 	});
 
-	fetching = false;
+	fetching.value = false;
 });
 </script>
 
diff --git a/packages/frontend/src/pages/admin/overview.heatmap.vue b/packages/frontend/src/pages/admin/overview.heatmap.vue
index 4d09f183bf..8e3c809353 100644
--- a/packages/frontend/src/pages/admin/overview.heatmap.vue
+++ b/packages/frontend/src/pages/admin/overview.heatmap.vue
@@ -17,10 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import MkHeatmap from '@/components/MkHeatmap.vue';
 import MkSelect from '@/components/MkSelect.vue';
 
-let src = $ref('active-users');
+const src = ref('active-users');
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/overview.moderators.vue b/packages/frontend/src/pages/admin/overview.moderators.vue
index 4086ca51f0..c6e81b4a18 100644
--- a/packages/frontend/src/pages/admin/overview.moderators.vue
+++ b/packages/frontend/src/pages/admin/overview.moderators.vue
@@ -17,21 +17,21 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
 
-let moderators: any = $ref(null);
-let fetching = $ref(true);
+const moderators = ref<any>(null);
+const fetching = ref(true);
 
 onMounted(async () => {
-	moderators = await os.api('admin/show-users', {
+	moderators.value = await os.api('admin/show-users', {
 		sort: '+lastActiveDate',
 		state: 'adminOrModerator',
 		limit: 30,
 	});
 
-	fetching = false;
+	fetching.value = false;
 });
 </script>
 
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index 1af9d89f62..b6b3bf194a 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { markRaw, onMounted, onUnmounted, ref } from 'vue';
+import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import XChart from './overview.queue.chart.vue';
 import number from '@/filters/number.js';
 import { useStream } from '@/stream.js';
@@ -46,10 +46,10 @@ const activeSincePrevTick = ref(0);
 const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
-let chartProcess = $shallowRef<InstanceType<typeof XChart>>();
-let chartActive = $shallowRef<InstanceType<typeof XChart>>();
-let chartDelayed = $shallowRef<InstanceType<typeof XChart>>();
-let chartWaiting = $shallowRef<InstanceType<typeof XChart>>();
+const chartProcess = shallowRef<InstanceType<typeof XChart>>();
+const chartActive = shallowRef<InstanceType<typeof XChart>>();
+const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
+const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
 
 const props = defineProps<{
 	domain: string;
@@ -61,10 +61,10 @@ const onStats = (stats) => {
 	delayed.value = stats[props.domain].delayed;
 	waiting.value = stats[props.domain].waiting;
 
-	chartProcess.pushData(stats[props.domain].activeSincePrevTick);
-	chartActive.pushData(stats[props.domain].active);
-	chartDelayed.pushData(stats[props.domain].delayed);
-	chartWaiting.pushData(stats[props.domain].waiting);
+	chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
+	chartActive.value.pushData(stats[props.domain].active);
+	chartDelayed.value.pushData(stats[props.domain].delayed);
+	chartWaiting.value.pushData(stats[props.domain].waiting);
 };
 
 const onStatsLog = (statsLog) => {
@@ -80,10 +80,10 @@ const onStatsLog = (statsLog) => {
 		dataWaiting.push(stats[props.domain].waiting);
 	}
 
-	chartProcess.setData(dataProcess);
-	chartActive.setData(dataActive);
-	chartDelayed.setData(dataDelayed);
-	chartWaiting.setData(dataWaiting);
+	chartProcess.value.setData(dataProcess);
+	chartActive.value.setData(dataActive);
+	chartDelayed.value.setData(dataDelayed);
+	chartWaiting.value.setData(dataWaiting);
 };
 
 onMounted(() => {
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index 5899177efa..ea8cb164cd 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as os from '@/os.js';
 import MkNumberDiff from '@/components/MkNumberDiff.vue';
 import MkNumber from '@/components/MkNumber.vue';
@@ -69,29 +69,29 @@ import { i18n } from '@/i18n.js';
 import { customEmojis } from '@/custom-emojis.js';
 import { defaultStore } from '@/store.js';
 
-let stats: any = $ref(null);
-let usersComparedToThePrevDay = $ref<number>();
-let notesComparedToThePrevDay = $ref<number>();
-let onlineUsersCount = $ref(0);
-let fetching = $ref(true);
+const stats = ref<any>(null);
+const usersComparedToThePrevDay = ref<number>();
+const notesComparedToThePrevDay = ref<number>();
+const onlineUsersCount = ref(0);
+const fetching = ref(true);
 
 onMounted(async () => {
 	const [_stats, _onlineUsersCount] = await Promise.all([
 		os.api('stats', {}),
 		os.apiGet('get-online-users-count').then(res => res.count),
 	]);
-	stats = _stats;
-	onlineUsersCount = _onlineUsersCount;
+	stats.value = _stats;
+	onlineUsersCount.value = _onlineUsersCount;
 
 	os.apiGet('charts/users', { limit: 2, span: 'day' }).then(chart => {
-		usersComparedToThePrevDay = stats.originalUsersCount - chart.local.total[1];
+		usersComparedToThePrevDay.value = stats.value.originalUsersCount - chart.local.total[1];
 	});
 
 	os.apiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => {
-		notesComparedToThePrevDay = stats.originalNotesCount - chart.local.total[1];
+		notesComparedToThePrevDay.value = stats.value.originalNotesCount - chart.local.total[1];
 	});
 
-	fetching = false;
+	fetching.value = false;
 });
 </script>
 
diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue
index 6ee83c51e7..6b8dd90747 100644
--- a/packages/frontend/src/pages/admin/overview.users.vue
+++ b/packages/frontend/src/pages/admin/overview.users.vue
@@ -17,13 +17,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import * as os from '@/os.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import { defaultStore } from '@/store.js';
 
-let newUsers = $ref(null);
-let fetching = $ref(true);
+const newUsers = ref(null);
+const fetching = ref(true);
 
 const fetch = async () => {
 	const _newUsers = await os.api('admin/show-users', {
@@ -31,8 +32,8 @@ const fetch = async () => {
 		sort: '+createdAt',
 		origin: 'local',
 	});
-	newUsers = _newUsers;
-	fetching = false;
+	newUsers.value = _newUsers;
+	fetching.value = false;
 };
 
 useInterval(fetch, 1000 * 60, {
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 170dc0d212..8b7cad004f 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { markRaw, onMounted, onBeforeUnmount, nextTick } from 'vue';
+import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed } from 'vue';
 import XFederation from './overview.federation.vue';
 import XInstances from './overview.instances.vue';
 import XQueue from './overview.queue.vue';
@@ -82,16 +82,16 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 
-const rootEl = $shallowRef<HTMLElement>();
-let serverInfo: any = $ref(null);
-let topSubInstancesForPie: any = $ref(null);
-let topPubInstancesForPie: any = $ref(null);
-let federationPubActive = $ref<number | null>(null);
-let federationPubActiveDiff = $ref<number | null>(null);
-let federationSubActive = $ref<number | null>(null);
-let federationSubActiveDiff = $ref<number | null>(null);
-let newUsers = $ref(null);
-let activeInstances = $shallowRef(null);
+const rootEl = shallowRef<HTMLElement>();
+const serverInfo = ref<any>(null);
+const topSubInstancesForPie = ref<any>(null);
+const topPubInstancesForPie = ref<any>(null);
+const federationPubActive = ref<number | null>(null);
+const federationPubActiveDiff = ref<number | null>(null);
+const federationSubActive = ref<number | null>(null);
+const federationSubActiveDiff = ref<number | null>(null);
+const newUsers = ref(null);
+const activeInstances = shallowRef(null);
 const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
 const now = new Date();
 const filesPagination = {
@@ -116,14 +116,14 @@ onMounted(async () => {
 	*/
 
 	os.apiGet('charts/federation', { limit: 2, span: 'day' }).then(chart => {
-		federationPubActive = chart.pubActive[0];
-		federationPubActiveDiff = chart.pubActive[0] - chart.pubActive[1];
-		federationSubActive = chart.subActive[0];
-		federationSubActiveDiff = chart.subActive[0] - chart.subActive[1];
+		federationPubActive.value = chart.pubActive[0];
+		federationPubActiveDiff.value = chart.pubActive[0] - chart.pubActive[1];
+		federationSubActive.value = chart.subActive[0];
+		federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
 	});
 
 	os.apiGet('federation/stats', { limit: 10 }).then(res => {
-		topSubInstancesForPie = res.topSubInstances.map(x => ({
+		topSubInstancesForPie.value = res.topSubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followersCount,
@@ -131,7 +131,7 @@ onMounted(async () => {
 				os.pageWindow(`/instance-info/${x.host}`);
 			},
 		})).concat([{ name: '(other)', color: '#80808080', value: res.otherFollowersCount }]);
-		topPubInstancesForPie = res.topPubInstances.map(x => ({
+		topPubInstancesForPie.value = res.topPubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followingCount,
@@ -142,21 +142,21 @@ onMounted(async () => {
 	});
 
 	os.api('admin/server-info').then(serverInfoResponse => {
-		serverInfo = serverInfoResponse;
+		serverInfo.value = serverInfoResponse;
 	});
 
 	os.api('admin/show-users', {
 		limit: 5,
 		sort: '+createdAt',
 	}).then(res => {
-		newUsers = res;
+		newUsers.value = res;
 	});
 
 	os.api('federation/instances', {
 		sort: '+latestRequestReceivedAt',
 		limit: 25,
 	}).then(res => {
-		activeInstances = res;
+		activeInstances.value = res;
 	});
 
 	nextTick(() => {
@@ -171,9 +171,9 @@ onBeforeUnmount(() => {
 	queueStatsConnection.dispose();
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.dashboard,
diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue
index 9681215aa1..4fdecbb67e 100644
--- a/packages/frontend/src/pages/admin/proxy-account.vue
+++ b/packages/frontend/src/pages/admin/proxy-account.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -31,36 +31,36 @@ import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let proxyAccount: any = $ref(null);
-let proxyAccountId: any = $ref(null);
+const proxyAccount = ref<any>(null);
+const proxyAccountId = ref<any>(null);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	proxyAccountId = meta.proxyAccountId;
-	if (proxyAccountId) {
-		proxyAccount = await os.api('users/show', { userId: proxyAccountId });
+	proxyAccountId.value = meta.proxyAccountId;
+	if (proxyAccountId.value) {
+		proxyAccount.value = await os.api('users/show', { userId: proxyAccountId.value });
 	}
 }
 
 function chooseProxyAccount() {
 	os.selectUser().then(user => {
-		proxyAccount = user;
-		proxyAccountId = user.id;
+		proxyAccount.value = user;
+		proxyAccountId.value = user.id;
 		save();
 	});
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		proxyAccountId: proxyAccountId,
+		proxyAccountId: proxyAccountId.value,
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.proxyAccount,
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index d9f4af454d..a8fc2f391c 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { markRaw, onMounted, onUnmounted, ref } from 'vue';
+import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import XChart from './queue.chart.chart.vue';
 import number from '@/filters/number.js';
 import * as os from '@/os.js';
@@ -63,10 +63,10 @@ const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
 const jobs = ref([]);
-let chartProcess = $shallowRef<InstanceType<typeof XChart>>();
-let chartActive = $shallowRef<InstanceType<typeof XChart>>();
-let chartDelayed = $shallowRef<InstanceType<typeof XChart>>();
-let chartWaiting = $shallowRef<InstanceType<typeof XChart>>();
+const chartProcess = shallowRef<InstanceType<typeof XChart>>();
+const chartActive = shallowRef<InstanceType<typeof XChart>>();
+const chartDelayed = shallowRef<InstanceType<typeof XChart>>();
+const chartWaiting = shallowRef<InstanceType<typeof XChart>>();
 
 const props = defineProps<{
 	domain: string;
@@ -78,10 +78,10 @@ const onStats = (stats) => {
 	delayed.value = stats[props.domain].delayed;
 	waiting.value = stats[props.domain].waiting;
 
-	chartProcess.pushData(stats[props.domain].activeSincePrevTick);
-	chartActive.pushData(stats[props.domain].active);
-	chartDelayed.pushData(stats[props.domain].delayed);
-	chartWaiting.pushData(stats[props.domain].waiting);
+	chartProcess.value.pushData(stats[props.domain].activeSincePrevTick);
+	chartActive.value.pushData(stats[props.domain].active);
+	chartDelayed.value.pushData(stats[props.domain].delayed);
+	chartWaiting.value.pushData(stats[props.domain].waiting);
 };
 
 const onStatsLog = (statsLog) => {
@@ -97,10 +97,10 @@ const onStatsLog = (statsLog) => {
 		dataWaiting.push(stats[props.domain].waiting);
 	}
 
-	chartProcess.setData(dataProcess);
-	chartActive.setData(dataActive);
-	chartDelayed.setData(dataDelayed);
-	chartWaiting.setData(dataWaiting);
+	chartProcess.value.setData(dataProcess);
+	chartActive.value.setData(dataActive);
+	chartDelayed.value.setData(dataDelayed);
+	chartWaiting.value.setData(dataWaiting);
 };
 
 onMounted(() => {
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index ece54ab12b..f07fba8d15 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -16,6 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref, computed } from 'vue';
 import XQueue from './queue.chart.vue';
 import XHeader from './_header_.vue';
 import * as os from '@/os.js';
@@ -24,7 +25,7 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 
-let tab = $ref('deliver');
+const tab = ref('deliver');
 
 function clear() {
 	os.confirm({
@@ -46,11 +47,11 @@ function promoteAllQueues() {
 	}).then(({ canceled }) => {
 		if (canceled) return;
 
-		os.apiWithDialog('admin/queue/promote', { type: tab });
+		os.apiWithDialog('admin/queue/promote', { type: tab.value });
 	});
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-external-link',
 	text: i18n.ts.dashboard,
@@ -59,7 +60,7 @@ const headerActions = $computed(() => [{
 	},
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'deliver',
 	title: 'Deliver',
 }, {
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index 8100d8188b..b97eca33d2 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -24,14 +24,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let relays: any[] = $ref([]);
+const relays = ref<any[]>([]);
 
 async function addRelay() {
 	const { canceled, result: inbox } = await os.inputText({
@@ -67,20 +67,20 @@ function remove(inbox: string) {
 
 function refresh() {
 	os.api('admin/relays/list').then((relayList: any) => {
-		relays = relayList;
+		relays.value = relayList;
 	});
 }
 
 refresh();
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-plus',
 	text: i18n.ts.addRelay,
 	handler: addRelay,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.relays,
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 29726e805b..16db8403ed 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XHeader from './_header_.vue';
 import XEditor from './roles.editor.vue';
@@ -39,17 +39,17 @@ const props = defineProps<{
 	id?: string;
 }>();
 
-let role = $ref(null);
-let data = $ref(null);
+const role = ref(null);
+const data = ref(null);
 
 if (props.id) {
-	role = await os.api('admin/roles/show', {
+	role.value = await os.api('admin/roles/show', {
 		roleId: props.id,
 	});
 
-	data = role;
+	data.value = role.value;
 } else {
-	data = {
+	data.value = {
 		name: 'New Role',
 		description: '',
 		isAdministrator: false,
@@ -69,24 +69,24 @@ if (props.id) {
 
 async function save() {
 	rolesCache.delete();
-	if (role) {
+	if (role.value) {
 		os.apiWithDialog('admin/roles/update', {
-			roleId: role.id,
-			...data,
+			roleId: role.value.id,
+			...data.value,
 		});
-		router.push('/admin/roles/' + role.id);
+		router.push('/admin/roles/' + role.value.id);
 	} else {
 		const created = await os.apiWithDialog('admin/roles/create', {
-			...data,
+			...data.value,
 		});
 		router.push('/admin/roles/' + created.id);
 	}
 }
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => role ? {
-	title: i18n.ts._role.edit + ': ' + role.name,
+definePageMetadata(computed(() => role.value ? {
+	title: i18n.ts._role.edit + ': ' + role.value.name,
 	icon: 'ti ti-badge',
 } : {
 	title: i18n.ts._role.new,
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 1db99e61f4..a8e0e8bbd1 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -278,7 +278,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</MkRange>
 				</div>
 			</MkFolder>
-			
+
 			<MkFolder v-if="matchQuery([i18n.ts._role._options.canSearchNotes, 'canSearchNotes'])">
 				<template #label>{{ i18n.ts._role._options.canSearchNotes }}</template>
 				<template #suffix>
@@ -537,7 +537,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, computed } from 'vue';
 import { throttle } from 'throttle-debounce';
 import RolesEditorFormula from './RolesEditorFormula.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -562,12 +562,12 @@ const props = defineProps<{
 	readonly?: boolean;
 }>();
 
-let role = $ref(deepClone(props.modelValue));
+const role = ref(deepClone(props.modelValue));
 
 // fill missing policy
 for (const ROLE_POLICY of ROLE_POLICIES) {
-	if (role.policies[ROLE_POLICY] == null) {
-		role.policies[ROLE_POLICY] = {
+	if (role.value.policies[ROLE_POLICY] == null) {
+		role.value.policies[ROLE_POLICY] = {
 			useDefault: true,
 			priority: 0,
 			value: instance.policies[ROLE_POLICY],
@@ -575,15 +575,15 @@ for (const ROLE_POLICY of ROLE_POLICIES) {
 	}
 }
 
-let rolePermission = $computed({
-	get: () => role.isAdministrator ? 'administrator' : role.isModerator ? 'moderator' : 'normal',
+const rolePermission = computed({
+	get: () => role.value.isAdministrator ? 'administrator' : role.value.isModerator ? 'moderator' : 'normal',
 	set: (val) => {
-		role.isAdministrator = val === 'administrator';
-		role.isModerator = val === 'moderator';
+		role.value.isAdministrator = val === 'administrator';
+		role.value.isModerator = val === 'moderator';
 	},
 });
 
-let q = $ref('');
+const q = ref('');
 
 function getPriorityIcon(option) {
 	if (option.priority === 2) return 'ti ti-arrows-up';
@@ -592,32 +592,32 @@ function getPriorityIcon(option) {
 }
 
 function matchQuery(keywords: string[]): boolean {
-	if (q.trim().length === 0) return true;
-	return keywords.some(keyword => keyword.toLowerCase().includes(q.toLowerCase()));
+	if (q.value.trim().length === 0) return true;
+	return keywords.some(keyword => keyword.toLowerCase().includes(q.value.toLowerCase()));
 }
 
 const save = throttle(100, () => {
 	const data = {
-		name: role.name,
-		description: role.description,
-		color: role.color === '' ? null : role.color,
-		iconUrl: role.iconUrl === '' ? null : role.iconUrl,
-		displayOrder: role.displayOrder,
-		target: role.target,
-		condFormula: role.condFormula,
-		isAdministrator: role.isAdministrator,
-		isModerator: role.isModerator,
-		isPublic: role.isPublic,
-		isExplorable: role.isExplorable,
-		asBadge: role.asBadge,
-		canEditMembersByModerator: role.canEditMembersByModerator,
-		policies: role.policies,
+		name: role.value.name,
+		description: role.value.description,
+		color: role.value.color === '' ? null : role.value.color,
+		iconUrl: role.value.iconUrl === '' ? null : role.value.iconUrl,
+		displayOrder: role.value.displayOrder,
+		target: role.value.target,
+		condFormula: role.value.condFormula,
+		isAdministrator: role.value.isAdministrator,
+		isModerator: role.value.isModerator,
+		isPublic: role.value.isPublic,
+		isExplorable: role.value.isExplorable,
+		asBadge: role.value.asBadge,
+		canEditMembersByModerator: role.value.canEditMembersByModerator,
+		policies: role.value.policies,
 	};
 
 	emit('update:modelValue', data);
 });
 
-watch($$(role), save, { deep: true });
+watch(role, save, { deep: true });
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 0b02f419bc..c11cc24b4f 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, reactive } from 'vue';
+import { computed, reactive, ref } from 'vue';
 import XHeader from './_header_.vue';
 import XEditor from './roles.editor.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -90,7 +90,7 @@ const usersPagination = {
 	})),
 };
 
-let expandedItems = $ref([]);
+const expandedItems = ref([]);
 
 const role = reactive(await os.api('admin/roles/show', {
 	roleId: props.id,
@@ -160,16 +160,16 @@ async function unassign(user, ev) {
 }
 
 async function toggleItem(item) {
-	if (expandedItems.includes(item.id)) {
-		expandedItems = expandedItems.filter(x => x !== item.id);
+	if (expandedItems.value.includes(item.id)) {
+		expandedItems.value = expandedItems.value.filter(x => x !== item.id);
 	} else {
-		expandedItems.push(item.id);
+		expandedItems.value.push(item.id);
 	}
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.role + ': ' + role.name,
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 91cd86485f..db4595b150 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -258,9 +258,9 @@ function create() {
 	router.push('/admin/roles/new');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.roles,
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index f7f76d910a..9835591fa8 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -114,7 +114,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XBotProtection from './bot-protection.vue';
 import XHeader from './_header_.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -129,65 +129,65 @@ import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let summalyProxy: string = $ref('');
-let enableHcaptcha: boolean = $ref(false);
-let enableRecaptcha: boolean = $ref(false);
-let enableTurnstile: boolean = $ref(false);
-let sensitiveMediaDetection: string = $ref('none');
-let sensitiveMediaDetectionSensitivity: number = $ref(0);
-let setSensitiveFlagAutomatically: boolean = $ref(false);
-let enableSensitiveMediaDetectionForVideos: boolean = $ref(false);
-let enableIpLogging: boolean = $ref(false);
-let enableActiveEmailValidation: boolean = $ref(false);
-let enableVerifymailApi: boolean = $ref(false);
-let verifymailAuthKey: string | null = $ref(null);
+const summalyProxy = ref<string>('');
+const enableHcaptcha = ref<boolean>(false);
+const enableRecaptcha = ref<boolean>(false);
+const enableTurnstile = ref<boolean>(false);
+const sensitiveMediaDetection = ref<string>('none');
+const sensitiveMediaDetectionSensitivity = ref<number>(0);
+const setSensitiveFlagAutomatically = ref<boolean>(false);
+const enableSensitiveMediaDetectionForVideos = ref<boolean>(false);
+const enableIpLogging = ref<boolean>(false);
+const enableActiveEmailValidation = ref<boolean>(false);
+const enableVerifymailApi = ref<boolean>(false);
+const verifymailAuthKey = ref<string | null>(null);
 
 async function init() {
 	const meta = await os.api('admin/meta');
-	summalyProxy = meta.summalyProxy;
-	enableHcaptcha = meta.enableHcaptcha;
-	enableRecaptcha = meta.enableRecaptcha;
-	enableTurnstile = meta.enableTurnstile;
-	sensitiveMediaDetection = meta.sensitiveMediaDetection;
-	sensitiveMediaDetectionSensitivity =
+	summalyProxy.value = meta.summalyProxy;
+	enableHcaptcha.value = meta.enableHcaptcha;
+	enableRecaptcha.value = meta.enableRecaptcha;
+	enableTurnstile.value = meta.enableTurnstile;
+	sensitiveMediaDetection.value = meta.sensitiveMediaDetection;
+	sensitiveMediaDetectionSensitivity.value =
 		meta.sensitiveMediaDetectionSensitivity === 'veryLow' ? 0 :
 		meta.sensitiveMediaDetectionSensitivity === 'low' ? 1 :
 		meta.sensitiveMediaDetectionSensitivity === 'medium' ? 2 :
 		meta.sensitiveMediaDetectionSensitivity === 'high' ? 3 :
 		meta.sensitiveMediaDetectionSensitivity === 'veryHigh' ? 4 : 0;
-	setSensitiveFlagAutomatically = meta.setSensitiveFlagAutomatically;
-	enableSensitiveMediaDetectionForVideos = meta.enableSensitiveMediaDetectionForVideos;
-	enableIpLogging = meta.enableIpLogging;
-	enableActiveEmailValidation = meta.enableActiveEmailValidation;
-	enableVerifymailApi = meta.enableVerifymailApi;
-	verifymailAuthKey = meta.verifymailAuthKey;
+	setSensitiveFlagAutomatically.value = meta.setSensitiveFlagAutomatically;
+	enableSensitiveMediaDetectionForVideos.value = meta.enableSensitiveMediaDetectionForVideos;
+	enableIpLogging.value = meta.enableIpLogging;
+	enableActiveEmailValidation.value = meta.enableActiveEmailValidation;
+	enableVerifymailApi.value = meta.enableVerifymailApi;
+	verifymailAuthKey.value = meta.verifymailAuthKey;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		summalyProxy,
-		sensitiveMediaDetection,
+		summalyProxy: summalyProxy.value,
+		sensitiveMediaDetection: sensitiveMediaDetection.value,
 		sensitiveMediaDetectionSensitivity:
-			sensitiveMediaDetectionSensitivity === 0 ? 'veryLow' :
-			sensitiveMediaDetectionSensitivity === 1 ? 'low' :
-			sensitiveMediaDetectionSensitivity === 2 ? 'medium' :
-			sensitiveMediaDetectionSensitivity === 3 ? 'high' :
-			sensitiveMediaDetectionSensitivity === 4 ? 'veryHigh' :
+			sensitiveMediaDetectionSensitivity.value === 0 ? 'veryLow' :
+			sensitiveMediaDetectionSensitivity.value === 1 ? 'low' :
+			sensitiveMediaDetectionSensitivity.value === 2 ? 'medium' :
+			sensitiveMediaDetectionSensitivity.value === 3 ? 'high' :
+			sensitiveMediaDetectionSensitivity.value === 4 ? 'veryHigh' :
 			0,
-		setSensitiveFlagAutomatically,
-		enableSensitiveMediaDetectionForVideos,
-		enableIpLogging,
-		enableActiveEmailValidation,
-		enableVerifymailApi,
-		verifymailAuthKey,
+		setSensitiveFlagAutomatically: setSensitiveFlagAutomatically.value,
+		enableSensitiveMediaDetectionForVideos: enableSensitiveMediaDetectionForVideos.value,
+		enableIpLogging: enableIpLogging.value,
+		enableActiveEmailValidation: enableActiveEmailValidation.value,
+		enableVerifymailApi: enableVerifymailApi.value,
+		verifymailAuthKey: verifymailAuthKey.value,
 	}).then(() => {
 		fetchInstance();
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.security,
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index 4cd1f6cbec..231f4ba56f 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import * as os from '@/os.js';
 import { fetchInstance, instance } from '@/instance.js';
@@ -52,20 +52,20 @@ import MkInput from '@/components/MkInput.vue';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
-let serverRules: string[] = $ref(instance.serverRules);
+const serverRules = ref<string[]>(instance.serverRules);
 
 const save = async () => {
 	await os.apiWithDialog('admin/update-meta', {
-		serverRules,
+		serverRules: serverRules.value,
 	});
 	fetchInstance();
 };
 
 const remove = (index: number): void => {
-	serverRules.splice(index, 1);
+	serverRules.value.splice(index, 1);
 };
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.serverRules,
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 86fbfa0827..f17284efa9 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -148,7 +148,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -163,76 +163,76 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
 
-let name: string | null = $ref(null);
-let shortName: string | null = $ref(null);
-let description: string | null = $ref(null);
-let maintainerName: string | null = $ref(null);
-let maintainerEmail: string | null = $ref(null);
-let impressumUrl: string | null = $ref(null);
-let pinnedUsers: string = $ref('');
-let cacheRemoteFiles: boolean = $ref(false);
-let cacheRemoteSensitiveFiles: boolean = $ref(false);
-let enableServiceWorker: boolean = $ref(false);
-let swPublicKey: any = $ref(null);
-let swPrivateKey: any = $ref(null);
-let enableFanoutTimeline: boolean = $ref(false);
-let enableFanoutTimelineDbFallback: boolean = $ref(false);
-let perLocalUserUserTimelineCacheMax: number = $ref(0);
-let perRemoteUserUserTimelineCacheMax: number = $ref(0);
-let perUserHomeTimelineCacheMax: number = $ref(0);
-let perUserListTimelineCacheMax: number = $ref(0);
-let notesPerOneAd: number = $ref(0);
+const name = ref<string | null>(null);
+const shortName = ref<string | null>(null);
+const description = ref<string | null>(null);
+const maintainerName = ref<string | null>(null);
+const maintainerEmail = ref<string | null>(null);
+const impressumUrl = ref<string | null>(null);
+const pinnedUsers = ref<string>('');
+const cacheRemoteFiles = ref<boolean>(false);
+const cacheRemoteSensitiveFiles = ref<boolean>(false);
+const enableServiceWorker = ref<boolean>(false);
+const swPublicKey = ref<any>(null);
+const swPrivateKey = ref<any>(null);
+const enableFanoutTimeline = ref<boolean>(false);
+const enableFanoutTimelineDbFallback = ref<boolean>(false);
+const perLocalUserUserTimelineCacheMax = ref<number>(0);
+const perRemoteUserUserTimelineCacheMax = ref<number>(0);
+const perUserHomeTimelineCacheMax = ref<number>(0);
+const perUserListTimelineCacheMax = ref<number>(0);
+const notesPerOneAd = ref<number>(0);
 
 async function init(): Promise<void> {
 	const meta = await os.api('admin/meta');
-	name = meta.name;
-	shortName = meta.shortName;
-	description = meta.description;
-	maintainerName = meta.maintainerName;
-	maintainerEmail = meta.maintainerEmail;
-	impressumUrl = meta.impressumUrl;
-	pinnedUsers = meta.pinnedUsers.join('\n');
-	cacheRemoteFiles = meta.cacheRemoteFiles;
-	cacheRemoteSensitiveFiles = meta.cacheRemoteSensitiveFiles;
-	enableServiceWorker = meta.enableServiceWorker;
-	swPublicKey = meta.swPublickey;
-	swPrivateKey = meta.swPrivateKey;
-	enableFanoutTimeline = meta.enableFanoutTimeline;
-	enableFanoutTimelineDbFallback = meta.enableFanoutTimelineDbFallback;
-	perLocalUserUserTimelineCacheMax = meta.perLocalUserUserTimelineCacheMax;
-	perRemoteUserUserTimelineCacheMax = meta.perRemoteUserUserTimelineCacheMax;
-	perUserHomeTimelineCacheMax = meta.perUserHomeTimelineCacheMax;
-	perUserListTimelineCacheMax = meta.perUserListTimelineCacheMax;
-	notesPerOneAd = meta.notesPerOneAd;
+	name.value = meta.name;
+	shortName.value = meta.shortName;
+	description.value = meta.description;
+	maintainerName.value = meta.maintainerName;
+	maintainerEmail.value = meta.maintainerEmail;
+	impressumUrl.value = meta.impressumUrl;
+	pinnedUsers.value = meta.pinnedUsers.join('\n');
+	cacheRemoteFiles.value = meta.cacheRemoteFiles;
+	cacheRemoteSensitiveFiles.value = meta.cacheRemoteSensitiveFiles;
+	enableServiceWorker.value = meta.enableServiceWorker;
+	swPublicKey.value = meta.swPublickey;
+	swPrivateKey.value = meta.swPrivateKey;
+	enableFanoutTimeline.value = meta.enableFanoutTimeline;
+	enableFanoutTimelineDbFallback.value = meta.enableFanoutTimelineDbFallback;
+	perLocalUserUserTimelineCacheMax.value = meta.perLocalUserUserTimelineCacheMax;
+	perRemoteUserUserTimelineCacheMax.value = meta.perRemoteUserUserTimelineCacheMax;
+	perUserHomeTimelineCacheMax.value = meta.perUserHomeTimelineCacheMax;
+	perUserListTimelineCacheMax.value = meta.perUserListTimelineCacheMax;
+	notesPerOneAd.value = meta.notesPerOneAd;
 }
 
 async function save(): void {
 	await os.apiWithDialog('admin/update-meta', {
-		name,
-		shortName: shortName === '' ? null : shortName,
-		description,
-		maintainerName,
-		maintainerEmail,
-		impressumUrl,
-		pinnedUsers: pinnedUsers.split('\n'),
-		cacheRemoteFiles,
-		cacheRemoteSensitiveFiles,
-		enableServiceWorker,
-		swPublicKey,
-		swPrivateKey,
-		enableFanoutTimeline,
-		enableFanoutTimelineDbFallback,
-		perLocalUserUserTimelineCacheMax,
-		perRemoteUserUserTimelineCacheMax,
-		perUserHomeTimelineCacheMax,
-		perUserListTimelineCacheMax,
-		notesPerOneAd,
+		name: name.value,
+		shortName: shortName.value === '' ? null : shortName.value,
+		description: description.value,
+		maintainerName: maintainerName.value,
+		maintainerEmail: maintainerEmail.value,
+		impressumUrl: impressumUrl.value,
+		pinnedUsers: pinnedUsers.value.split('\n'),
+		cacheRemoteFiles: cacheRemoteFiles.value,
+		cacheRemoteSensitiveFiles: cacheRemoteSensitiveFiles.value,
+		enableServiceWorker: enableServiceWorker.value,
+		swPublicKey: swPublicKey.value,
+		swPrivateKey: swPrivateKey.value,
+		enableFanoutTimeline: enableFanoutTimeline.value,
+		enableFanoutTimelineDbFallback: enableFanoutTimelineDbFallback.value,
+		perLocalUserUserTimelineCacheMax: perLocalUserUserTimelineCacheMax.value,
+		perRemoteUserUserTimelineCacheMax: perRemoteUserUserTimelineCacheMax.value,
+		perUserHomeTimelineCacheMax: perUserHomeTimelineCacheMax.value,
+		perUserListTimelineCacheMax: perUserListTimelineCacheMax.value,
+		notesPerOneAd: notesPerOneAd.value,
 	});
 
 	fetchInstance();
 }
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.general,
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index fcf41de734..ea4c231af2 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, shallowRef, ref } from 'vue';
 import XHeader from './_header_.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
@@ -69,22 +69,22 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import { dateString } from '@/filters/date.js';
 
-let paginationComponent = $shallowRef<InstanceType<typeof MkPagination>>();
+const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
-let sort = $ref('+createdAt');
-let state = $ref('all');
-let origin = $ref('local');
-let searchUsername = $ref('');
-let searchHost = $ref('');
+const sort = ref('+createdAt');
+const state = ref('all');
+const origin = ref('local');
+const searchUsername = ref('');
+const searchHost = ref('');
 const pagination = {
 	endpoint: 'admin/show-users' as const,
 	limit: 10,
 	params: computed(() => ({
-		sort: sort,
-		state: state,
-		origin: origin,
-		username: searchUsername,
-		hostname: searchHost,
+		sort: sort.value,
+		state: state.value,
+		origin: origin.value,
+		username: searchUsername.value,
+		hostname: searchHost.value,
 	})),
 	offsetMode: true,
 };
@@ -111,7 +111,7 @@ async function addUser() {
 		username: username,
 		password: password,
 	}).then(res => {
-		paginationComponent.reload();
+		paginationComponent.value.reload();
 	});
 }
 
@@ -119,7 +119,7 @@ function show(user) {
 	os.pageWindow(`/admin/user/${user.id}`);
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	icon: 'ti ti-search',
 	text: i18n.ts.search,
 	handler: searchUser,
@@ -135,7 +135,7 @@ const headerActions = $computed(() => [{
 	handler: lookupUser,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.users,
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index afc6a98281..8eca403707 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -40,7 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -90,9 +90,9 @@ async function read(announcement) {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'current',
 	title: i18n.ts.currentAnnouncements,
 	icon: 'ti ti-flare',
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 4ea91796d5..3d7ecdacf6 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref, shallowRef } from 'vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import { scroll } from '@/scripts/scroll.js';
 import * as os from '@/os.js';
@@ -38,20 +38,20 @@ const props = defineProps<{
 	antennaId: string;
 }>();
 
-let antenna = $ref(null);
-let queue = $ref(0);
-let rootEl = $shallowRef<HTMLElement>();
-let tlEl = $shallowRef<InstanceType<typeof MkTimeline>>();
-const keymap = $computed(() => ({
+const antenna = ref(null);
+const queue = ref(0);
+const rootEl = shallowRef<HTMLElement>();
+const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
+const keymap = computed(() => ({
 	't': focus,
 }));
 
 function queueUpdated(q) {
-	queue = q;
+	queue.value = q;
 }
 
 function top() {
-	scroll(rootEl, { top: 0 });
+	scroll(rootEl.value, { top: 0 });
 }
 
 async function timetravel() {
@@ -60,7 +60,7 @@ async function timetravel() {
 	});
 	if (canceled) return;
 
-	tlEl.timetravel(date);
+	tlEl.value.timetravel(date);
 }
 
 function settings() {
@@ -68,16 +68,16 @@ function settings() {
 }
 
 function focus() {
-	tlEl.focus();
+	tlEl.value.focus();
 }
 
 watch(() => props.antennaId, async () => {
-	antenna = await os.api('antennas/show', {
+	antenna.value = await os.api('antennas/show', {
 		antennaId: props.antennaId,
 	});
 }, { immediate: true });
 
-const headerActions = $computed(() => antenna ? [{
+const headerActions = computed(() => antenna.value ? [{
 	icon: 'ti ti-calendar-time',
 	text: i18n.ts.jumpToSpecifiedDate,
 	handler: timetravel,
@@ -87,10 +87,10 @@ const headerActions = $computed(() => antenna ? [{
 	handler: settings,
 }] : []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => antenna ? {
-	title: antenna.name,
+definePageMetadata(computed(() => antenna.value ? {
+	title: antenna.value.name,
 	icon: 'ti ti-antenna',
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue
index 01657c4c2a..5374c220de 100644
--- a/packages/frontend/src/pages/api-console.vue
+++ b/packages/frontend/src/pages/api-console.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import JSON5 from 'json5';
 import { Endpoints } from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
@@ -83,9 +83,9 @@ function onEndpointChange() {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: 'API console',
diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue
index 9d39a1e603..8a17e5895d 100644
--- a/packages/frontend/src/pages/auth.form.vue
+++ b/packages/frontend/src/pages/auth.form.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
@@ -35,11 +35,11 @@ const emit = defineEmits<{
 	(event: 'denied'): void;
 }>();
 
-const app = $computed(() => props.session.app);
+const app = computed(() => props.session.app);
 
-const name = $computed(() => {
+const name = computed(() => {
 	const el = document.createElement('div');
-	el.textContent = app.name;
+	el.textContent = app.value.name;
 	return el.innerHTML;
 });
 
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index bcd54a6ed5..1b342647fb 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import XForm from './auth.form.vue';
 import MkSignin from '@/components/MkSignin.vue';
@@ -55,15 +55,15 @@ const props = defineProps<{
 	token: string;
 }>();
 
-let state = $ref<'waiting' | 'accepted' | 'fetch-session-error' | 'denied' | null>(null);
-let session = $ref<Misskey.entities.AuthSessionShowResponse | null>(null);
+const state = ref<'waiting' | 'accepted' | 'fetch-session-error' | 'denied' | null>(null);
+const session = ref<Misskey.entities.AuthSessionShowResponse | null>(null);
 
 function accepted() {
-	state = 'accepted';
-	if (session && session.app.callbackUrl) {
-		const url = new URL(session.app.callbackUrl);
+	state.value = 'accepted';
+	if (session.value && session.value.app.callbackUrl) {
+		const url = new URL(session.value.app.callbackUrl);
 		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
-		location.href = `${session.app.callbackUrl}?token=${session.token}`;
+		location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`;
 	}
 }
 
@@ -75,27 +75,27 @@ onMounted(async () => {
 	if (!$i) return;
 
 	try {
-		session = await os.api('auth/session/show', {
+		session.value = await os.api('auth/session/show', {
 			token: props.token,
 		});
 
 		// 既に連携していた場合
-		if (session.app.isAuthorized) {
+		if (session.value.app.isAuthorized) {
 			await os.api('auth/accept', {
-				token: session.token,
+				token: session.value.token,
 			});
 			accepted();
 		} else {
-			state = 'waiting';
+			state.value = 'waiting';
 		}
 	} catch (err) {
-		state = 'fetch-session-error';
+		state.value = 'fetch-session-error';
 	}
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts._auth.shareAccessTitle,
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index 715f234493..b9edb18d10 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -46,10 +46,10 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkFolder from '@/components/MkFolder.vue';
 
-let avatarDecorations: any[] = $ref([]);
+const avatarDecorations = ref<any[]>([]);
 
 function add() {
-	avatarDecorations.unshift({
+	avatarDecorations.value.unshift({
 		_id: Math.random().toString(36),
 		id: null,
 		name: '',
@@ -64,7 +64,7 @@ function del(avatarDecoration) {
 		text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
-		avatarDecorations = avatarDecorations.filter(x => x !== avatarDecoration);
+		avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
 		os.api('admin/avatar-decorations/delete', avatarDecoration);
 	});
 }
@@ -80,20 +80,20 @@ async function save(avatarDecoration) {
 
 function load() {
 	os.api('admin/avatar-decorations/list').then(_avatarDecorations => {
-		avatarDecorations = _avatarDecorations;
+		avatarDecorations.value = _avatarDecorations;
 	});
 }
 
 load();
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-plus',
 	text: i18n.ts.add,
 	handler: add,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.avatarDecorations,
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 5256ea4f11..af382bb137 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -90,22 +90,22 @@ const props = defineProps<{
 	channelId?: string;
 }>();
 
-let channel = $ref(null);
-let name = $ref(null);
-let description = $ref(null);
-let bannerUrl = $ref<string | null>(null);
-let bannerId = $ref<string | null>(null);
-let color = $ref('#000');
-let isSensitive = $ref(false);
-let allowRenoteToExternal = $ref(true);
+const channel = ref(null);
+const name = ref(null);
+const description = ref(null);
+const bannerUrl = ref<string | null>(null);
+const bannerId = ref<string | null>(null);
+const color = ref('#000');
+const isSensitive = ref(false);
+const allowRenoteToExternal = ref(true);
 const pinnedNotes = ref([]);
 
-watch(() => bannerId, async () => {
-	if (bannerId == null) {
-		bannerUrl = null;
+watch(() => bannerId.value, async () => {
+	if (bannerId.value == null) {
+		bannerUrl.value = null;
 	} else {
-		bannerUrl = (await os.api('drive/files/show', {
-			fileId: bannerId,
+		bannerUrl.value = (await os.api('drive/files/show', {
+			fileId: bannerId.value,
 		})).url;
 	}
 });
@@ -113,20 +113,20 @@ watch(() => bannerId, async () => {
 async function fetchChannel() {
 	if (props.channelId == null) return;
 
-	channel = await os.api('channels/show', {
+	channel.value = await os.api('channels/show', {
 		channelId: props.channelId,
 	});
 
-	name = channel.name;
-	description = channel.description;
-	bannerId = channel.bannerId;
-	bannerUrl = channel.bannerUrl;
-	isSensitive = channel.isSensitive;
-	pinnedNotes.value = channel.pinnedNoteIds.map(id => ({
+	name.value = channel.value.name;
+	description.value = channel.value.description;
+	bannerId.value = channel.value.bannerId;
+	bannerUrl.value = channel.value.bannerUrl;
+	isSensitive.value = channel.value.isSensitive;
+	pinnedNotes.value = channel.value.pinnedNoteIds.map(id => ({
 		id,
 	}));
-	color = channel.color;
-	allowRenoteToExternal = channel.allowRenoteToExternal;
+	color.value = channel.value.color;
+	allowRenoteToExternal.value = channel.value.allowRenoteToExternal;
 }
 
 fetchChannel();
@@ -150,13 +150,13 @@ function removePinnedNote(index: number) {
 
 function save() {
 	const params = {
-		name: name,
-		description: description,
-		bannerId: bannerId,
+		name: name.value,
+		description: description.value,
+		bannerId: bannerId.value,
 		pinnedNoteIds: pinnedNotes.value.map(x => x.id),
-		color: color,
-		isSensitive: isSensitive,
-		allowRenoteToExternal: allowRenoteToExternal,
+		color: color.value,
+		isSensitive: isSensitive.value,
+		allowRenoteToExternal: allowRenoteToExternal.value,
 	};
 
 	if (props.channelId) {
@@ -172,7 +172,7 @@ function save() {
 async function archive() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		title: i18n.t('channelArchiveConfirmTitle', { name: name }),
+		title: i18n.t('channelArchiveConfirmTitle', { name: name.value }),
 		text: i18n.ts.channelArchiveConfirmDescription,
 	});
 
@@ -188,17 +188,17 @@ async function archive() {
 
 function setBannerImage(evt) {
 	selectFile(evt.currentTarget ?? evt.target, null).then(file => {
-		bannerId = file.id;
+		bannerId.value = file.id;
 	});
 }
 
 function removeBannerImage() {
-	bannerId = null;
+	bannerId.value = null;
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => props.channelId ? {
 	title: i18n.ts._channel.edit,
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index dc374e2925..698f7fa383 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import MkPostForm from '@/components/MkPostForm.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
@@ -96,13 +96,13 @@ const props = defineProps<{
 	channelId: string;
 }>();
 
-let tab = $ref('overview');
-let channel = $ref(null);
-let favorited = $ref(false);
-let searchQuery = $ref('');
-let searchPagination = $ref();
-let searchKey = $ref('');
-const featuredPagination = $computed(() => ({
+const tab = ref('overview');
+const channel = ref(null);
+const favorited = ref(false);
+const searchQuery = ref('');
+const searchPagination = ref();
+const searchKey = ref('');
+const featuredPagination = computed(() => ({
 	endpoint: 'notes/featured' as const,
 	limit: 10,
 	params: {
@@ -111,30 +111,30 @@ const featuredPagination = $computed(() => ({
 }));
 
 watch(() => props.channelId, async () => {
-	channel = await os.api('channels/show', {
+	channel.value = await os.api('channels/show', {
 		channelId: props.channelId,
 	});
-	favorited = channel.isFavorited;
-	if (favorited || channel.isFollowing) {
-		tab = 'timeline';
+	favorited.value = channel.value.isFavorited;
+	if (favorited.value || channel.value.isFollowing) {
+		tab.value = 'timeline';
 	}
 }, { immediate: true });
 
 function edit() {
-	router.push(`/channels/${channel.id}/edit`);
+	router.push(`/channels/${channel.value.id}/edit`);
 }
 
 function openPostForm() {
 	os.post({
-		channel,
+		channel: channel.value,
 	});
 }
 
 function favorite() {
 	os.apiWithDialog('channels/favorite', {
-		channelId: channel.id,
+		channelId: channel.value.id,
 	}).then(() => {
-		favorited = true;
+		favorited.value = true;
 	});
 }
 
@@ -145,38 +145,38 @@ async function unfavorite() {
 	});
 	if (confirm.canceled) return;
 	os.apiWithDialog('channels/unfavorite', {
-		channelId: channel.id,
+		channelId: channel.value.id,
 	}).then(() => {
-		favorited = false;
+		favorited.value = false;
 	});
 }
 
 async function search() {
-	const query = searchQuery.toString().trim();
+	const query = searchQuery.value.toString().trim();
 
 	if (query == null) return;
 
-	searchPagination = {
+	searchPagination.value = {
 		endpoint: 'notes/search',
 		limit: 10,
 		params: {
 			query: query,
-			channelId: channel.id,
+			channelId: channel.value.id,
 		},
 	};
 
-	searchKey = query;
+	searchKey.value = query;
 }
 
-const headerActions = $computed(() => {
-	if (channel && channel.userId) {
+const headerActions = computed(() => {
+	if (channel.value && channel.value.userId) {
 		const headerItems: PageHeaderItem[] = [];
 
 		headerItems.push({
 			icon: 'ti ti-link',
 			text: i18n.ts.copyUrl,
 			handler: async (): Promise<void> => {
-				copyToClipboard(`${url}/channels/${channel.id}`);
+				copyToClipboard(`${url}/channels/${channel.value.id}`);
 				os.success();
 			},
 		});
@@ -187,15 +187,15 @@ const headerActions = $computed(() => {
 				text: i18n.ts.share,
 				handler: async (): Promise<void> => {
 					navigator.share({
-						title: channel.name,
-						text: channel.description,
-						url: `${url}/channels/${channel.id}`,
+						title: channel.value.name,
+						text: channel.value.description,
+						url: `${url}/channels/${channel.value.id}`,
 					});
 				},
 			});
 		}
 
-		if (($i && $i.id === channel.userId) || iAmModerator) {
+		if (($i && $i.id === channel.value.userId) || iAmModerator) {
 			headerItems.push({
 				icon: 'ti ti-settings',
 				text: i18n.ts.edit,
@@ -209,7 +209,7 @@ const headerActions = $computed(() => {
 	}
 });
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'overview',
 	title: i18n.ts.overview,
 	icon: 'ti ti-info-circle',
@@ -227,8 +227,8 @@ const headerTabs = $computed(() => [{
 	icon: 'ti ti-search',
 }]);
 
-definePageMetadata(computed(() => channel ? {
-	title: channel.name,
+definePageMetadata(computed(() => channel.value ? {
+	title: channel.value.name,
 	icon: 'ti ti-device-tv',
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 8219a947f2..e58c89bb77 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -50,7 +50,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import MkChannelPreview from '@/components/MkChannelPreview.vue';
 import MkChannelList from '@/components/MkChannelList.vue';
 import MkPagination from '@/components/MkPagination.vue';
@@ -69,15 +69,15 @@ const props = defineProps<{
 	type?: string;
 }>();
 
-let key = $ref('');
-let tab = $ref('featured');
-let searchQuery = $ref('');
-let searchType = $ref('nameAndDescription');
-let channelPagination = $ref();
+const key = ref('');
+const tab = ref('featured');
+const searchQuery = ref('');
+const searchType = ref('nameAndDescription');
+const channelPagination = ref();
 
 onMounted(() => {
-	searchQuery = props.query ?? '';
-	searchType = props.type ?? 'nameAndDescription';
+	searchQuery.value = props.query ?? '';
+	searchType.value = props.type ?? 'nameAndDescription';
 });
 
 const featuredPagination = {
@@ -99,35 +99,35 @@ const ownedPagination = {
 };
 
 async function search() {
-	const query = searchQuery.toString().trim();
+	const query = searchQuery.value.toString().trim();
 
 	if (query == null) return;
 
-	const type = searchType.toString().trim();
+	const type = searchType.value.toString().trim();
 
-	channelPagination = {
+	channelPagination.value = {
 		endpoint: 'channels/search',
 		limit: 10,
 		params: {
-			query: searchQuery,
+			query: searchQuery.value,
 			type: type,
 		},
 	};
 
-	key = query + type;
+	key.value = query + type;
 }
 
 function create() {
 	router.push('/channels/new');
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	icon: 'ti ti-plus',
 	text: i18n.ts.create,
 	handler: create,
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'search',
 	title: i18n.ts.search,
 	icon: 'ti ti-search',
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index b32c8a3864..2ea0312c7e 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, provide } from 'vue';
+import { computed, watch, provide, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkNotes from '@/components/MkNotes.vue';
 import { $i } from '@/account.js';
@@ -43,8 +43,8 @@ const props = defineProps<{
 	clipId: string,
 }>();
 
-let clip: Misskey.entities.Clip = $ref<Misskey.entities.Clip>();
-let favorited = $ref(false);
+const clip = ref<Misskey.entities.Clip | null>(null);
+const favorited = ref(false);
 const pagination = {
 	endpoint: 'clips/notes' as const,
 	limit: 10,
@@ -53,24 +53,24 @@ const pagination = {
 	})),
 };
 
-const isOwned: boolean | null = $computed<boolean | null>(() => $i && clip && ($i.id === clip.userId));
+const isOwned = computed<boolean | null>(() => $i && clip.value && ($i.id === clip.value.userId));
 
 watch(() => props.clipId, async () => {
-	clip = await os.api('clips/show', {
+	clip.value = await os.api('clips/show', {
 		clipId: props.clipId,
 	});
-	favorited = clip.isFavorited;
+	favorited.value = clip.value.isFavorited;
 }, {
 	immediate: true,
 });
 
-provide('currentClip', $$(clip));
+provide('currentClip', clip);
 
 function favorite() {
 	os.apiWithDialog('clips/favorite', {
 		clipId: props.clipId,
 	}).then(() => {
-		favorited = true;
+		favorited.value = true;
 	});
 }
 
@@ -83,57 +83,57 @@ async function unfavorite() {
 	os.apiWithDialog('clips/unfavorite', {
 		clipId: props.clipId,
 	}).then(() => {
-		favorited = false;
+		favorited.value = false;
 	});
 }
 
-const headerActions = $computed(() => clip && isOwned ? [{
+const headerActions = computed(() => clip.value && isOwned.value ? [{
 	icon: 'ti ti-pencil',
 	text: i18n.ts.edit,
 	handler: async (): Promise<void> => {
-		const { canceled, result } = await os.form(clip.name, {
+		const { canceled, result } = await os.form(clip.value.name, {
 			name: {
 				type: 'string',
 				label: i18n.ts.name,
-				default: clip.name,
+				default: clip.value.name,
 			},
 			description: {
 				type: 'string',
 				required: false,
 				multiline: true,
 				label: i18n.ts.description,
-				default: clip.description,
+				default: clip.value.description,
 			},
 			isPublic: {
 				type: 'boolean',
 				label: i18n.ts.public,
-				default: clip.isPublic,
+				default: clip.value.isPublic,
 			},
 		});
 		if (canceled) return;
 
 		os.apiWithDialog('clips/update', {
-			clipId: clip.id,
+			clipId: clip.value.id,
 			...result,
 		});
 
 		clipsCache.delete();
 	},
-}, ...(clip.isPublic ? [{
+}, ...(clip.value.isPublic ? [{
 	icon: 'ti ti-link',
 	text: i18n.ts.copyUrl,
 	handler: async (): Promise<void> => {
-		copyToClipboard(`${url}/clips/${clip.id}`);
+		copyToClipboard(`${url}/clips/${clip.value.id}`);
 		os.success();
 	},
-}] : []), ...(clip.isPublic && isSupportShare() ? [{
+}] : []), ...(clip.value.isPublic && isSupportShare() ? [{
 	icon: 'ti ti-share',
 	text: i18n.ts.share,
 	handler: async (): Promise<void> => {
 		navigator.share({
-			title: clip.name,
-			text: clip.description,
-			url: `${url}/clips/${clip.id}`,
+			title: clip.value.name,
+			text: clip.value.description,
+			url: `${url}/clips/${clip.value.id}`,
 		});
 	},
 }] : []), {
@@ -143,20 +143,20 @@ const headerActions = $computed(() => clip && isOwned ? [{
 	handler: async (): Promise<void> => {
 		const { canceled } = await os.confirm({
 			type: 'warning',
-			text: i18n.t('deleteAreYouSure', { x: clip.name }),
+			text: i18n.t('deleteAreYouSure', { x: clip.value.name }),
 		});
 		if (canceled) return;
 
 		await os.apiWithDialog('clips/delete', {
-			clipId: clip.id,
+			clipId: clip.value.id,
 		});
 
 		clipsCache.delete();
 	},
 }] : null);
 
-definePageMetadata(computed(() => clip ? {
-	title: clip.name,
+definePageMetadata(computed(() => clip.value ? {
+	title: clip.value.name,
 	icon: 'ti ti-paperclip',
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index 316dbaa3aa..fa92424fa0 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -286,7 +286,7 @@ const delBulk = async () => {
 	emojisPaginationComponent.value.reload();
 };
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-plus',
 	text: i18n.ts.addEmoji,
@@ -296,7 +296,7 @@ const headerActions = $computed(() => [{
 	handler: menu,
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'local',
 	title: i18n.ts.local,
 }, {
diff --git a/packages/frontend/src/pages/drive.vue b/packages/frontend/src/pages/drive.vue
index 54fb83fc1d..64fbd16971 100644
--- a/packages/frontend/src/pages/drive.vue
+++ b/packages/frontend/src/pages/drive.vue
@@ -10,19 +10,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import XDrive from '@/components/MkDrive.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let folder = $ref(null);
+const folder = ref(null);
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
-	title: folder ? folder.name : i18n.ts.drive,
+	title: folder.value ? folder.value.name : i18n.ts.drive,
 	icon: 'ti ti-cloud',
 	hideHeader: true,
 })));
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index 5bce74aa94..8119150df9 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -74,7 +74,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -92,22 +92,22 @@ const props = defineProps<{
 	emoji?: any,
 }>();
 
-let dialog = $ref(null);
-let name: string = $ref(props.emoji ? props.emoji.name : '');
-let category: string = $ref(props.emoji ? props.emoji.category : '');
-let aliases: string = $ref(props.emoji ? props.emoji.aliases.join(' ') : '');
-let license: string = $ref(props.emoji ? (props.emoji.license ?? '') : '');
-let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false);
-let localOnly = $ref(props.emoji ? props.emoji.localOnly : false);
-let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
-let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
-let file = $ref<Misskey.entities.DriveFile>();
+const dialog = ref(null);
+const name = ref<string>(props.emoji ? props.emoji.name : '');
+const category = ref<string>(props.emoji ? props.emoji.category : '');
+const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
+const license = ref<string>(props.emoji ? (props.emoji.license ?? '') : '');
+const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false);
+const localOnly = ref(props.emoji ? props.emoji.localOnly : false);
+const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
+const rolesThatCanBeUsedThisEmojiAsReaction = ref([]);
+const file = ref<Misskey.entities.DriveFile>();
 
-watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
-	rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
+watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
+	rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
 }, { immediate: true });
 
-const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
+const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
 
 const emit = defineEmits<{
 	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
@@ -115,42 +115,42 @@ const emit = defineEmits<{
 }>();
 
 async function changeImage(ev) {
-	file = await selectFile(ev.currentTarget ?? ev.target, null);
-	const candidate = file.name.replace(/\.(.+)$/, '');
+	file.value = await selectFile(ev.currentTarget ?? ev.target, null);
+	const candidate = file.value.name.replace(/\.(.+)$/, '');
 	if (candidate.match(/^[a-z0-9_]+$/)) {
-		name = candidate;
+		name.value = candidate;
 	}
 }
 
 async function addRole() {
 	const roles = await os.api('admin/roles/list');
-	const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id);
+	const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id);
 
 	const { canceled, result: role } = await os.select({
 		items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
 	});
 	if (canceled) return;
 
-	rolesThatCanBeUsedThisEmojiAsReaction.push(role);
+	rolesThatCanBeUsedThisEmojiAsReaction.value.push(role);
 }
 
 async function removeRole(role, ev) {
-	rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
+	rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id);
 }
 
 async function done() {
 	const params = {
-		name,
-		category: category === '' ? null : category,
-		aliases: aliases.split(' ').filter(x => x !== ''),
-		license: license === '' ? null : license,
-		isSensitive,
-		localOnly,
-		roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
+		name: name.value,
+		category: category.value === '' ? null : category.value,
+		aliases: aliases.value.split(' ').filter(x => x !== ''),
+		license: license.value === '' ? null : license.value,
+		isSensitive: isSensitive.value,
+		localOnly: localOnly.value,
+		roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id),
 	};
 
-	if (file) {
-		params.fileId = file.id;
+	if (file.value) {
+		params.fileId = file.value.id;
 	}
 
 	if (props.emoji) {
@@ -166,7 +166,7 @@ async function done() {
 			},
 		});
 
-		dialog.close();
+		dialog.value.close();
 	} else {
 		const created = await os.apiWithDialog('admin/emoji/add', params);
 
@@ -174,14 +174,14 @@ async function done() {
 			created: created,
 		});
 
-		dialog.close();
+		dialog.value.close();
 	}
 }
 
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: name }),
+		text: i18n.t('removeAreYouSure', { x: name.value }),
 	});
 	if (canceled) return;
 
@@ -191,7 +191,7 @@ async function del() {
 		emit('done', {
 			deleted: true,
 		});
-		dialog.close();
+		dialog.value.close();
 	});
 }
 </script>
diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue
index a36d1b3bda..000371528e 100644
--- a/packages/frontend/src/pages/explore.featured.vue
+++ b/packages/frontend/src/pages/explore.featured.vue
@@ -15,6 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import MkNotes from '@/components/MkNotes.vue';
 import MkTab from '@/components/MkTab.vue';
 import { i18n } from '@/i18n.js';
@@ -30,5 +31,5 @@ const paginationForPolls = {
 	offsetMode: true,
 };
 
-let tab = $ref('notes');
+const tab = ref('notes');
 </script>
diff --git a/packages/frontend/src/pages/explore.roles.vue b/packages/frontend/src/pages/explore.roles.vue
index 995ccd777c..929da19426 100644
--- a/packages/frontend/src/pages/explore.roles.vue
+++ b/packages/frontend/src/pages/explore.roles.vue
@@ -12,14 +12,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import MkRolePreview from '@/components/MkRolePreview.vue';
 import * as os from '@/os.js';
 
-let roles = $ref();
+const roles = ref();
 
 os.api('roles/list').then(res => {
-	roles = res.filter(x => x.target === 'manual').sort((a, b) => b.displayOrder - a.displayOrder);
+	roles.value = res.filter(x => x.target === 'manual').sort((a, b) => b.displayOrder - a.displayOrder);
 });
 </script>
 
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index 1f187d6b8a..ffebd4cd6c 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, shallowRef, computed } from 'vue';
 import MkUserList from '@/components/MkUserList.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkTab from '@/components/MkTab.vue';
@@ -74,16 +74,16 @@ const props = defineProps<{
 	tag?: string;
 }>();
 
-let origin = $ref('local');
-let tagsEl = $shallowRef<InstanceType<typeof MkFoldableSection>>();
-let tagsLocal = $ref([]);
-let tagsRemote = $ref([]);
+const origin = ref('local');
+const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
+const tagsLocal = ref([]);
+const tagsRemote = ref([]);
 
 watch(() => props.tag, () => {
-	if (tagsEl) tagsEl.toggleContent(props.tag == null);
+	if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
 });
 
-const tagUsers = $computed(() => ({
+const tagUsers = computed(() => ({
 	endpoint: 'hashtags/users' as const,
 	limit: 30,
 	params: {
@@ -127,13 +127,13 @@ os.api('hashtags/list', {
 	attachedToLocalUserOnly: true,
 	limit: 30,
 }).then(tags => {
-	tagsLocal = tags;
+	tagsLocal.value = tags;
 });
 os.api('hashtags/list', {
 	sort: '+attachedRemoteUsers',
 	attachedToRemoteUserOnly: true,
 	limit: 30,
 }).then(tags => {
-	tagsRemote = tags;
+	tagsRemote.value = tags;
 });
 </script>
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index fd846d979a..f068de8880 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref, shallowRef } from 'vue';
 import XFeatured from './explore.featured.vue';
 import XUsers from './explore.users.vue';
 import XRoles from './explore.roles.vue';
@@ -36,16 +36,16 @@ const props = withDefaults(defineProps<{
 	initialTab: 'featured',
 });
 
-let tab = $ref(props.initialTab);
-let tagsEl = $shallowRef<InstanceType<typeof MkFoldableSection>>();
+const tab = ref(props.initialTab);
+const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
 
 watch(() => props.tag, () => {
-	if (tagsEl) tagsEl.toggleContent(props.tag == null);
+	if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'featured',
 	icon: 'ti ti-bolt',
 	title: i18n.ts.featured,
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index f494218c18..cfda4d6556 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -363,79 +363,79 @@ const props = defineProps<{
 	id?: string;
 }>();
 
-let flash = $ref(null);
-let visibility = $ref('public');
+const flash = ref(null);
+const visibility = ref('public');
 
 if (props.id) {
-	flash = await os.api('flash/show', {
+	flash.value = await os.api('flash/show', {
 		flashId: props.id,
 	});
 }
 
-let title = $ref(flash?.title ?? 'New Play');
-let summary = $ref(flash?.summary ?? '');
-let permissions = $ref(flash?.permissions ?? []);
-let script = $ref(flash?.script ?? PRESET_DEFAULT);
+const title = ref(flash.value?.title ?? 'New Play');
+const summary = ref(flash.value?.summary ?? '');
+const permissions = ref(flash.value?.permissions ?? []);
+const script = ref(flash.value?.script ?? PRESET_DEFAULT);
 
 function selectPreset(ev: MouseEvent) {
 	os.popupMenu([{
 		text: 'Omikuji',
 		action: () => {
-			script = PRESET_OMIKUJI;
+			script.value = PRESET_OMIKUJI;
 		},
 	}, {
 		text: 'Shuffle',
 		action: () => {
-			script = PRESET_SHUFFLE;
+			script.value = PRESET_SHUFFLE;
 		},
 	}, {
 		text: 'Quiz',
 		action: () => {
-			script = PRESET_QUIZ;
+			script.value = PRESET_QUIZ;
 		},
 	}, {
 		text: 'Timeline viewer',
 		action: () => {
-			script = PRESET_TIMELINE;
+			script.value = PRESET_TIMELINE;
 		},
 	}], ev.currentTarget ?? ev.target);
 }
 
 async function save() {
-	if (flash) {
+	if (flash.value) {
 		os.apiWithDialog('flash/update', {
 			flashId: props.id,
-			title,
-			summary,
-			permissions,
-			script,
-			visibility,
+			title: title.value,
+			summary: summary.value,
+			permissions: permissions.value,
+			script: script.value,
+			visibility: visibility.value,
 		});
 	} else {
 		const created = await os.apiWithDialog('flash/create', {
-			title,
-			summary,
-			permissions,
-			script,
+			title: title.value,
+			summary: summary.value,
+			permissions: permissions.value,
+			script: script.value,
 		});
 		router.push('/play/' + created.id + '/edit');
 	}
 }
 
 function show() {
-	if (flash == null) {
+	if (flash.value == null) {
 		os.alert({
 			text: 'Please save',
 		});
 	} else {
-		os.pageWindow(`/play/${flash.id}`);
+		os.pageWindow(`/play/${flash.value.id}`);
 	}
 }
 
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: flash.title }),
+		text: i18n.t('deleteAreYouSure', { x: flash.value.title }),
 	});
 	if (canceled) return;
 
@@ -445,12 +445,12 @@ async function del() {
 	router.push('/play');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => flash ? {
-	title: i18n.ts._play.edit + ': ' + flash.title,
+definePageMetadata(computed(() => flash.value ? {
+	title: i18n.ts._play.edit + ': ' + flash.value.title,
 } : {
 	title: i18n.ts._play.new,
 }));
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index 4a686efd45..e0b9f87d46 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import MkFlashPreview from '@/components/MkFlashPreview.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -48,7 +48,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const router = useRouter();
 
-let tab = $ref('featured');
+const tab = ref('featured');
 
 const featuredFlashsPagination = {
 	endpoint: 'flash/featured' as const,
@@ -67,13 +67,13 @@ function create() {
 	router.push('/play/new');
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	icon: 'ti ti-plus',
 	text: i18n.ts.create,
 	handler: create,
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'featured',
 	title: i18n.ts._play.featured,
 	icon: 'ti ti-flare',
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 4755eb5062..0ac95ca282 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onDeactivated, onUnmounted, Ref, ref, watch } from 'vue';
+import { computed, onDeactivated, onUnmounted, Ref, ref, watch, shallowRef } from 'vue';
 import { Interpreter, Parser, values } from '@syuilo/aiscript';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
@@ -78,45 +78,45 @@ const props = defineProps<{
 	id: string;
 }>();
 
-let flash = $ref(null);
-let error = $ref(null);
+const flash = ref(null);
+const error = ref(null);
 
 function fetchFlash() {
-	flash = null;
+	flash.value = null;
 	os.api('flash/show', {
 		flashId: props.id,
 	}).then(_flash => {
-		flash = _flash;
+		flash.value = _flash;
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
 function copyLink() {
-	copyToClipboard(`${url}/play/${flash.id}`);
+	copyToClipboard(`${url}/play/${flash.value.id}`);
 	os.success();
 }
 
 function share() {
 	navigator.share({
-		title: flash.title,
-		text: flash.summary,
-		url: `${url}/play/${flash.id}`,
+		title: flash.value.title,
+		text: flash.value.summary,
+		url: `${url}/play/${flash.value.id}`,
 	});
 }
 
 function shareWithNote() {
 	os.post({
-		initialText: `${flash.title} ${url}/play/${flash.id}`,
+		initialText: `${flash.value.title} ${url}/play/${flash.value.id}`,
 	});
 }
 
 function like() {
 	os.apiWithDialog('flash/like', {
-		flashId: flash.id,
+		flashId: flash.value.id,
 	}).then(() => {
-		flash.isLiked = true;
-		flash.likedCount++;
+		flash.value.isLiked = true;
+		flash.value.likedCount++;
 	});
 }
 
@@ -127,10 +127,10 @@ async function unlike() {
 	});
 	if (confirm.canceled) return;
 	os.apiWithDialog('flash/unlike', {
-		flashId: flash.id,
+		flashId: flash.value.id,
 	}).then(() => {
-		flash.isLiked = false;
-		flash.likedCount--;
+		flash.value.isLiked = false;
+		flash.value.likedCount--;
 	});
 }
 
@@ -138,28 +138,28 @@ watch(() => props.id, fetchFlash, { immediate: true });
 
 const parser = new Parser();
 
-let started = $ref(false);
-let aiscript = $shallowRef<Interpreter | null>(null);
+const started = ref(false);
+const aiscript = shallowRef<Interpreter | null>(null);
 const root = ref<AsUiRoot>();
-const components: Ref<AsUiComponent>[] = $ref([]);
+const components = ref<Ref<AsUiComponent>[]>([]);
 
 function start() {
-	started = true;
+	started.value = true;
 	run();
 }
 
 async function run() {
-	if (aiscript) aiscript.abort();
+	if (aiscript.value) aiscript.value.abort();
 
-	aiscript = new Interpreter({
+	aiscript.value = new Interpreter({
 		...createAiScriptEnv({
-			storageKey: 'flash:' + flash.id,
+			storageKey: 'flash:' + flash.value.id,
 		}),
-		...registerAsUiLib(components, (_root) => {
+		...registerAsUiLib(components.value, (_root) => {
 			root.value = _root.value;
 		}),
-		THIS_ID: values.STR(flash.id),
-		THIS_URL: values.STR(`${url}/play/${flash.id}`),
+		THIS_ID: values.STR(flash.value.id),
+		THIS_URL: values.STR(`${url}/play/${flash.value.id}`),
 	}, {
 		in: (q) => {
 			return new Promise(ok => {
@@ -184,7 +184,7 @@ async function run() {
 
 	let ast;
 	try {
-		ast = parser.parse(flash.script);
+		ast = parser.parse(flash.value.script);
 	} catch (err) {
 		os.alert({
 			type: 'error',
@@ -193,7 +193,7 @@ async function run() {
 		return;
 	}
 	try {
-		await aiscript.exec(ast);
+		await aiscript.value.exec(ast);
 	} catch (err) {
 		os.alert({
 			type: 'error',
@@ -204,24 +204,24 @@ async function run() {
 }
 
 onDeactivated(() => {
-	if (aiscript) aiscript.abort();
+	if (aiscript.value) aiscript.value.abort();
 });
 
 onUnmounted(() => {
-	if (aiscript) aiscript.abort();
+	if (aiscript.value) aiscript.value.abort();
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => flash ? {
-	title: flash.title,
-	avatar: flash.user,
-	path: `/play/${flash.id}`,
+definePageMetadata(computed(() => flash.value ? {
+	title: flash.value.title,
+	avatar: flash.value.user,
+	path: `/play/${flash.value.id}`,
 	share: {
-		title: flash.title,
-		text: flash.summary,
+		title: flash.value.title,
+		text: flash.value.summary,
 	},
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index 4f7fdef0ba..51f31b1ca5 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -65,9 +65,9 @@ function reject(user) {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.followRequests,
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index f3cbd4947c..5761e8e32c 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -56,38 +56,38 @@ const props = defineProps<{
 	postId?: string;
 }>();
 
-let init = $ref(null);
-let files = $ref([]);
-let description = $ref(null);
-let title = $ref(null);
-let isSensitive = $ref(false);
+const init = ref(null);
+const files = ref([]);
+const description = ref(null);
+const title = ref(null);
+const isSensitive = ref(false);
 
 function selectFile(evt) {
 	selectFiles(evt.currentTarget ?? evt.target, null).then(selected => {
-		files = files.concat(selected);
+		files.value = files.value.concat(selected);
 	});
 }
 
 function remove(file) {
-	files = files.filter(f => f.id !== file.id);
+	files.value = files.value.filter(f => f.id !== file.id);
 }
 
 async function save() {
 	if (props.postId) {
 		await os.apiWithDialog('gallery/posts/update', {
 			postId: props.postId,
-			title: title,
-			description: description,
-			fileIds: files.map(file => file.id),
-			isSensitive: isSensitive,
+			title: title.value,
+			description: description.value,
+			fileIds: files.value.map(file => file.id),
+			isSensitive: isSensitive.value,
 		});
 		router.push(`/gallery/${props.postId}`);
 	} else {
 		const created = await os.apiWithDialog('gallery/posts/create', {
-			title: title,
-			description: description,
-			fileIds: files.map(file => file.id),
-			isSensitive: isSensitive,
+			title: title.value,
+			description: description.value,
+			fileIds: files.value.map(file => file.id),
+			isSensitive: isSensitive.value,
 		});
 		router.push(`/gallery/${created.id}`);
 	}
@@ -106,19 +106,19 @@ async function del() {
 }
 
 watch(() => props.postId, () => {
-	init = () => props.postId ? os.api('gallery/posts/show', {
+	init.value = () => props.postId ? os.api('gallery/posts/show', {
 		postId: props.postId,
 	}).then(post => {
-		files = post.files;
-		title = post.title;
-		description = post.description;
-		isSensitive = post.isSensitive;
+		files.value = post.files;
+		title.value = post.title;
+		description.value = post.description;
+		isSensitive.value = post.isSensitive;
 	}) : Promise.resolve(null);
 }, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => props.postId ? {
 	title: i18n.ts.edit,
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 43bb7c496d..8d9ac07805 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, computed } from 'vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
@@ -61,9 +61,9 @@ const props = defineProps<{
 	tag?: string;
 }>();
 
-let tab = $ref('explore');
-let tags = $ref([]);
-let tagsRef = $ref();
+const tab = ref('explore');
+const tags = ref([]);
+const tagsRef = ref();
 
 const recentPostsPagination = {
 	endpoint: 'gallery/posts' as const,
@@ -82,7 +82,7 @@ const likedPostsPagination = {
 	limit: 5,
 };
 
-const tagUsersPagination = $computed(() => ({
+const tagUsersPagination = computed(() => ({
 	endpoint: 'hashtags/users' as const,
 	limit: 30,
 	params: {
@@ -93,10 +93,10 @@ const tagUsersPagination = $computed(() => ({
 }));
 
 watch(() => props.tag, () => {
-	if (tagsRef) tagsRef.tags.toggleContent(props.tag == null);
+	if (tagsRef.value) tagsRef.value.tags.toggleContent(props.tag == null);
 });
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	icon: 'ti ti-plus',
 	text: i18n.ts.create,
 	handler: () => {
@@ -104,7 +104,7 @@ const headerActions = $computed(() => [{
 	},
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'explore',
 	title: i18n.ts.gallery,
 	icon: 'ti ti-icons',
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index 5b551f75b5..3dd04ccb55 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -62,7 +62,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import MkContainer from '@/components/MkContainer.vue';
@@ -84,43 +84,43 @@ const props = defineProps<{
 	postId: string;
 }>();
 
-let post = $ref(null);
-let error = $ref(null);
+const post = ref(null);
+const error = ref(null);
 const otherPostsPagination = {
 	endpoint: 'users/gallery/posts' as const,
 	limit: 6,
 	params: computed(() => ({
-		userId: post.user.id,
+		userId: post.value.user.id,
 	})),
 };
 
 function fetchPost() {
-	post = null;
+	post.value = null;
 	os.api('gallery/posts/show', {
 		postId: props.postId,
 	}).then(_post => {
-		post = _post;
+		post.value = _post;
 	}).catch(_error => {
-		error = _error;
+		error.value = _error;
 	});
 }
 
 function copyLink() {
-	copyToClipboard(`${url}/gallery/${post.id}`);
+	copyToClipboard(`${url}/gallery/${post.value.id}`);
 	os.success();
 }
 
 function share() {
 	navigator.share({
-		title: post.title,
-		text: post.description,
-		url: `${url}/gallery/${post.id}`,
+		title: post.value.title,
+		text: post.value.description,
+		url: `${url}/gallery/${post.value.id}`,
 	});
 }
 
 function shareWithNote() {
 	os.post({
-		initialText: `${post.title} ${url}/gallery/${post.id}`,
+		initialText: `${post.value.title} ${url}/gallery/${post.value.id}`,
 	});
 }
 
@@ -128,8 +128,8 @@ function like() {
 	os.apiWithDialog('gallery/posts/like', {
 		postId: props.postId,
 	}).then(() => {
-		post.isLiked = true;
-		post.likedCount++;
+		post.value.isLiked = true;
+		post.value.likedCount++;
 	});
 }
 
@@ -142,28 +142,28 @@ async function unlike() {
 	os.apiWithDialog('gallery/posts/unlike', {
 		postId: props.postId,
 	}).then(() => {
-		post.isLiked = false;
-		post.likedCount--;
+		post.value.isLiked = false;
+		post.value.likedCount--;
 	});
 }
 
 function edit() {
-	router.push(`/gallery/${post.id}/edit`);
+	router.push(`/gallery/${post.value.id}/edit`);
 }
 
 watch(() => props.postId, fetchPost, { immediate: true });
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	icon: 'ti ti-pencil',
 	text: i18n.ts.edit,
 	handler: edit,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => post ? {
-	title: post.title,
-	avatar: post.user,
+definePageMetadata(computed(() => post.value ? {
+	title: post.value.title,
+	avatar: post.value.user,
 } : null));
 </script>
 
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 8706228fd1..93d74fb42e 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -117,7 +117,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkChart from '@/components/MkChart.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
@@ -142,14 +142,14 @@ const props = defineProps<{
 	host: string;
 }>();
 
-let tab = $ref('overview');
-let chartSrc = $ref('instance-requests');
-let meta = $ref<Misskey.entities.AdminMetaResponse | null>(null);
-let instance = $ref<Misskey.entities.FederationInstance | null>(null);
-let suspended = $ref(false);
-let isBlocked = $ref(false);
-let isSilenced = $ref(false);
-let faviconUrl = $ref<string | null>(null);
+const tab = ref('overview');
+const chartSrc = ref('instance-requests');
+const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
+const instance = ref<Misskey.entities.FederationInstance | null>(null);
+const suspended = ref(false);
+const isBlocked = ref(false);
+const isSilenced = ref(false);
+const faviconUrl = ref<string | null>(null);
 
 const usersPagination = {
 	endpoint: iAmModerator ? 'admin/show-users' : 'users' as const,
@@ -164,48 +164,48 @@ const usersPagination = {
 
 async function fetch(): Promise<void> {
 	if (iAmAdmin) {
-		meta = await os.api('admin/meta');
+		meta.value = await os.api('admin/meta');
 	}
-	instance = await os.api('federation/show-instance', {
+	instance.value = await os.api('federation/show-instance', {
 		host: props.host,
 	});
-	suspended = instance?.isSuspended ?? false;
-	isBlocked = instance?.isBlocked ?? false;
-	isSilenced = instance?.isSilenced ?? false;
-	faviconUrl = getProxiedImageUrlNullable(instance?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance?.iconUrl, 'preview');
+	suspended.value = instance.value?.isSuspended ?? false;
+	isBlocked.value = instance.value?.isBlocked ?? false;
+	isSilenced.value = instance.value?.isSilenced ?? false;
+	faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
 }
 
 async function toggleBlock(): Promise<void> {
-	if (!meta) throw new Error('No meta?');
-	if (!instance) throw new Error('No instance?');
-	const { host } = instance;
+	if (!meta.value) throw new Error('No meta?');
+	if (!instance.value) throw new Error('No instance?');
+	const { host } = instance.value;
 	await os.api('admin/update-meta', {
-		blockedHosts: isBlocked ? meta.blockedHosts.concat([host]) : meta.blockedHosts.filter(x => x !== host),
+		blockedHosts: isBlocked.value ? meta.value.blockedHosts.concat([host]) : meta.value.blockedHosts.filter(x => x !== host),
 	});
 }
 
 async function toggleSilenced(): Promise<void> {
-	if (!meta) throw new Error('No meta?');
-	if (!instance) throw new Error('No instance?');
-	const { host } = instance;
-	const silencedHosts = meta.silencedHosts ?? [];
+	if (!meta.value) throw new Error('No meta?');
+	if (!instance.value) throw new Error('No instance?');
+	const { host } = instance.value;
+	const silencedHosts = meta.value.silencedHosts ?? [];
 	await os.api('admin/update-meta', {
-		silencedHosts: isSilenced ? silencedHosts.concat([host]) : silencedHosts.filter(x => x !== host),
+		silencedHosts: isSilenced.value ? silencedHosts.concat([host]) : silencedHosts.filter(x => x !== host),
 	});
 }
 
 async function toggleSuspend(): Promise<void> {
-	if (!instance) throw new Error('No instance?');
+	if (!instance.value) throw new Error('No instance?');
 	await os.api('admin/federation/update-instance', {
-		host: instance.host,
-		isSuspended: suspended,
+		host: instance.value.host,
+		isSuspended: suspended.value,
 	});
 }
 
 function refreshMetadata(): void {
-	if (!instance) throw new Error('No instance?');
+	if (!instance.value) throw new Error('No instance?');
 	os.api('admin/federation/refresh-remote-instance-metadata', {
-		host: instance.host,
+		host: instance.value.host,
 	});
 	os.alert({
 		text: 'Refresh requested',
@@ -214,7 +214,7 @@ function refreshMetadata(): void {
 
 fetch();
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	text: `https://${props.host}`,
 	icon: 'ti ti-external-link',
 	handler: () => {
@@ -222,7 +222,7 @@ const headerActions = $computed(() => [{
 	},
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'overview',
 	title: i18n.ts.overview,
 	icon: 'ti ti-info-circle',
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
index db0b201b73..d951e8ce07 100644
--- a/packages/frontend/src/pages/list.vue
+++ b/packages/frontend/src/pages/list.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch, computed } from 'vue';
+import { watch, computed, ref } from 'vue';
 import * as os from '@/os.js';
 import { userPage } from '@/filters/user.js';
 import { i18n } from '@/i18n.js';
@@ -47,41 +47,41 @@ const props = defineProps<{
 	listId: string;
 }>();
 
-let list = $ref(null);
-let error = $ref();
-let users = $ref([]);
+const list = ref(null);
+const error = ref();
+const users = ref([]);
 
 function fetchList(): void {
 	os.api('users/lists/show', {
 		listId: props.listId,
 		forPublic: true,
 	}).then(_list => {
-		list = _list;
+		list.value = _list;
 		os.api('users/show', {
-			userIds: list.userIds,
+			userIds: list.value.userIds,
 		}).then(_users => {
-			users = _users;
+			users.value = _users;
 		});
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
 function like() {
 	os.apiWithDialog('users/lists/favorite', {
-		listId: list.id,
+		listId: list.value.id,
 	}).then(() => {
-		list.isLiked = true;
-		list.likedCount++;
+		list.value.isLiked = true;
+		list.value.likedCount++;
 	});
 }
 
 function unlike() {
 	os.apiWithDialog('users/lists/unfavorite', {
-		listId: list.id,
+		listId: list.value.id,
 	}).then(() => {
-		list.isLiked = false;
-		list.likedCount--;
+		list.value.isLiked = false;
+		list.value.likedCount--;
 	});
 }
 
@@ -90,17 +90,17 @@ async function create() {
 		title: i18n.ts.enterListName,
 	});
 	if (canceled) return;
-	await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.id });
+	await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.value.id });
 }
 
 watch(() => props.listId, fetchList, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list ? {
-	title: list.name,
+definePageMetadata(computed(() => list.value ? {
+	title: list.value.name,
 	icon: 'ti ti-list',
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index 1572d27aab..ad9bea4548 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkSignin from '@/components/MkSignin.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
@@ -61,10 +61,10 @@ const props = defineProps<{
 
 const _permissions = props.permission ? props.permission.split(',') : [];
 
-let state = $ref<string | null>(null);
+const state = ref<string | null>(null);
 
 async function accept(): Promise<void> {
-	state = 'waiting';
+	state.value = 'waiting';
 	await os.api('miauth/gen-token', {
 		session: props.session,
 		name: props.name,
@@ -72,7 +72,7 @@ async function accept(): Promise<void> {
 		permission: _permissions,
 	});
 
-	state = 'accepted';
+	state.value = 'accepted';
 	if (props.callback) {
 		const cbUrl = new URL(props.callback);
 		if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url');
@@ -82,16 +82,16 @@ async function accept(): Promise<void> {
 }
 
 function deny(): void {
-	state = 'denied';
+	state.value = 'denied';
 }
 
 function onLogin(res): void {
 	login(res.i);
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: 'MiAuth',
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index 6c963cdb5d..c5b1b54222 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import XAntenna from './editor.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -18,7 +19,7 @@ import { antennasCache } from '@/cache.js';
 
 const router = useRouter();
 
-let draft = $ref({
+const draft = ref({
 	name: '',
 	src: 'all',
 	userListId: null,
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index 6600ebcc64..896e61f289 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import XAntenna from './editor.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -19,7 +20,7 @@ import { antennasCache } from '@/cache';
 
 const router = useRouter();
 
-let antenna: any = $ref(null);
+const antenna = ref<any>(null);
 
 const props = defineProps<{
 	antennaId: string
@@ -31,7 +32,7 @@ function onAntennaUpdated() {
 }
 
 os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => {
-	antenna = antennaResponse;
+	antenna.value = antennaResponse;
 });
 
 definePageMetadata({
diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue
index 16b8b848fd..388096c7df 100644
--- a/packages/frontend/src/pages/my-antennas/editor.vue
+++ b/packages/frontend/src/pages/my-antennas/editor.vue
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -69,38 +69,38 @@ const emit = defineEmits<{
 	(ev: 'deleted'): void,
 }>();
 
-let name: string = $ref(props.antenna.name);
-let src: string = $ref(props.antenna.src);
-let userListId: any = $ref(props.antenna.userListId);
-let users: string = $ref(props.antenna.users.join('\n'));
-let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n'));
-let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
-let caseSensitive: boolean = $ref(props.antenna.caseSensitive);
-let localOnly: boolean = $ref(props.antenna.localOnly);
-let withReplies: boolean = $ref(props.antenna.withReplies);
-let withFile: boolean = $ref(props.antenna.withFile);
-let notify: boolean = $ref(props.antenna.notify);
-let userLists: any = $ref(null);
+const name = ref<string>(props.antenna.name);
+const src = ref<string>(props.antenna.src);
+const userListId = ref<any>(props.antenna.userListId);
+const users = ref<string>(props.antenna.users.join('\n'));
+const keywords = ref<string>(props.antenna.keywords.map(x => x.join(' ')).join('\n'));
+const excludeKeywords = ref<string>(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
+const caseSensitive = ref<boolean>(props.antenna.caseSensitive);
+const localOnly = ref<boolean>(props.antenna.localOnly);
+const withReplies = ref<boolean>(props.antenna.withReplies);
+const withFile = ref<boolean>(props.antenna.withFile);
+const notify = ref<boolean>(props.antenna.notify);
+const userLists = ref<any>(null);
 
-watch(() => src, async () => {
-	if (src === 'list' && userLists === null) {
-		userLists = await os.api('users/lists/list');
+watch(() => src.value, async () => {
+	if (src.value === 'list' && userLists.value === null) {
+		userLists.value = await os.api('users/lists/list');
 	}
 });
 
 async function saveAntenna() {
 	const antennaData = {
-		name,
-		src,
-		userListId,
-		withReplies,
-		withFile,
-		notify,
-		caseSensitive,
-		localOnly,
-		users: users.trim().split('\n').map(x => x.trim()),
-		keywords: keywords.trim().split('\n').map(x => x.trim().split(' ')),
-		excludeKeywords: excludeKeywords.trim().split('\n').map(x => x.trim().split(' ')),
+		name: name.value,
+		src: src.value,
+		userListId: userListId.value,
+		withReplies: withReplies.value,
+		withFile: withFile.value,
+		notify: notify.value,
+		caseSensitive: caseSensitive.value,
+		localOnly: localOnly.value,
+		users: users.value.trim().split('\n').map(x => x.trim()),
+		keywords: keywords.value.trim().split('\n').map(x => x.trim().split(' ')),
+		excludeKeywords: excludeKeywords.value.trim().split('\n').map(x => x.trim().split(' ')),
 	};
 
 	if (props.antenna.id == null) {
@@ -130,9 +130,9 @@ async function deleteAntenna() {
 
 function addUser() {
 	os.selectUser().then(user => {
-		users = users.trim();
-		users += '\n@' + Misskey.acct.toString(user as any);
-		users = users.trim();
+		users.value = users.value.trim();
+		users.value += '\n@' + Misskey.acct.toString(user as any);
+		users.value = users.value.trim();
 	});
 }
 </script>
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index 2d9cd05b45..4b2a3b548d 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -28,14 +28,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onActivated } from 'vue';
+import { onActivated, computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { antennasCache } from '@/cache';
 import { infoImageUrl } from '@/instance.js';
 
-const antennas = $computed(() => antennasCache.value.value ?? []);
+const antennas = computed(() => antennasCache.value.value ?? []);
 
 function fetch() {
 	antennasCache.fetch();
@@ -43,7 +43,7 @@ function fetch() {
 
 fetch();
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-refresh',
 	text: i18n.ts.reload,
@@ -53,7 +53,7 @@ const headerActions = $computed(() => [{
 	},
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.manageAntennas,
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index 8289d65a4b..2390617954 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, shallowRef, computed } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkClipPreview from '@/components/MkClipPreview.vue';
@@ -41,13 +41,13 @@ const pagination = {
 	limit: 10,
 };
 
-let tab = $ref('my');
-let favorites = $ref();
+const tab = ref('my');
+const favorites = ref();
 
-const pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
-watch($$(tab), async () => {
-	favorites = await os.api('clips/my-favorites');
+watch(tab, async () => {
+	favorites.value = await os.api('clips/my-favorites');
 });
 
 async function create() {
@@ -74,20 +74,20 @@ async function create() {
 
 	clipsCache.delete();
 
-	pagingComponent.reload();
+	pagingComponent.value.reload();
 }
 
 function onClipCreated() {
-	pagingComponent.reload();
+	pagingComponent.value.reload();
 }
 
 function onClipDeleted() {
-	pagingComponent.reload();
+	pagingComponent.value.reload();
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'my',
 	title: i18n.ts.myClips,
 	icon: 'ti ti-paperclip',
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 3e7efb5a7c..ff360fccfe 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onActivated } from 'vue';
+import { onActivated, computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkAvatars from '@/components/MkAvatars.vue';
 import * as os from '@/os.js';
@@ -39,7 +39,7 @@ import { userListsCache } from '@/cache';
 import { infoImageUrl } from '@/instance.js';
 import { $i } from '@/account.js';
 
-const items = $computed(() => userListsCache.value.value ?? []);
+const items = computed(() => userListsCache.value.value ?? []);
 
 function fetch() {
 	userListsCache.fetch();
@@ -57,7 +57,7 @@ async function create() {
 	fetch();
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-refresh',
 	text: i18n.ts.reload,
@@ -67,7 +67,7 @@ const headerActions = $computed(() => [{
 	},
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.manageLists,
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index b600f99fbc..3c6b0750de 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -79,7 +79,7 @@ const props = defineProps<{
 }>();
 
 const paginationEl = ref<InstanceType<typeof MkPagination>>();
-let list = $ref<Misskey.entities.UserList | null>(null);
+const list = ref<Misskey.entities.UserList | null>(null);
 const isPublic = ref(false);
 const name = ref('');
 const membershipsPagination = {
@@ -94,17 +94,17 @@ function fetchList() {
 	os.api('users/lists/show', {
 		listId: props.listId,
 	}).then(_list => {
-		list = _list;
-		name.value = list.name;
-		isPublic.value = list.isPublic;
+		list.value = _list;
+		name.value = list.value.name;
+		isPublic.value = list.value.isPublic;
 	});
 }
 
 function addUser() {
 	os.selectUser().then(user => {
-		if (!list) return;
+		if (!list.value) return;
 		os.apiWithDialog('users/lists/push', {
-			listId: list.id,
+			listId: list.value.id,
 			userId: user.id,
 		}).then(() => {
 			paginationEl.value.reload();
@@ -118,9 +118,9 @@ async function removeUser(item, ev) {
 		icon: 'ti ti-x',
 		danger: true,
 		action: async () => {
-			if (!list) return;
+			if (!list.value) return;
 			os.api('users/lists/pull', {
-				listId: list.id,
+				listId: list.value.id,
 				userId: item.userId,
 			}).then(() => {
 				paginationEl.value.removeItem(item.id);
@@ -135,7 +135,7 @@ async function showMembershipMenu(item, ev) {
 		icon: item.withReplies ? 'ti ti-messages-off' : 'ti ti-messages',
 		action: async () => {
 			os.api('users/lists/update-membership', {
-				listId: list.id,
+				listId: list.value.id,
 				userId: item.userId,
 				withReplies: !item.withReplies,
 			}).then(() => {
@@ -149,42 +149,42 @@ async function showMembershipMenu(item, ev) {
 }
 
 async function deleteList() {
-	if (!list) return;
+	if (!list.value) return;
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: list.name }),
+		text: i18n.t('removeAreYouSure', { x: list.value.name }),
 	});
 	if (canceled) return;
 
 	await os.apiWithDialog('users/lists/delete', {
-		listId: list.id,
+		listId: list.value.id,
 	});
 	userListsCache.delete();
 	mainRouter.push('/my/lists');
 }
 
 async function updateSettings() {
-	if (!list) return;
+	if (!list.value) return;
 	await os.apiWithDialog('users/lists/update', {
-		listId: list.id,
+		listId: list.value.id,
 		name: name.value,
 		isPublic: isPublic.value,
 	});
 
 	userListsCache.delete();
 
-	list.name = name.value;
-	list.isPublic = isPublic.value;
+	list.value.name = name.value;
+	list.value.isPublic = isPublic.value;
 }
 
 watch(() => props.listId, fetchList, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list ? {
-	title: list.name,
+definePageMetadata(computed(() => list.value ? {
+	title: list.value.name,
 	icon: 'ti ti-list',
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue
index b3d40e3ef8..2245147873 100644
--- a/packages/frontend/src/pages/not-found.vue
+++ b/packages/frontend/src/pages/not-found.vue
@@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
@@ -26,9 +27,9 @@ if (props.showLoginPopup) {
 	pleaseLogin('/');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.notFound,
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 066a3042ba..eee6dbfbb8 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import MkNotes from '@/components/MkNotes.vue';
@@ -61,18 +61,18 @@ const props = defineProps<{
 	noteId: string;
 }>();
 
-let note = $ref<null | Misskey.entities.Note>();
-let clips = $ref();
-let showPrev = $ref(false);
-let showNext = $ref(false);
-let error = $ref();
+const note = ref<null | Misskey.entities.Note>();
+const clips = ref();
+const showPrev = ref(false);
+const showNext = ref(false);
+const error = ref();
 
 const prevPagination = {
 	endpoint: 'users/notes' as const,
 	limit: 10,
-	params: computed(() => note ? ({
-		userId: note.userId,
-		untilId: note.id,
+	params: computed(() => note.value ? ({
+		userId: note.value.userId,
+		untilId: note.value.id,
 	}) : null),
 };
 
@@ -80,30 +80,30 @@ const nextPagination = {
 	reversed: true,
 	endpoint: 'users/notes' as const,
 	limit: 10,
-	params: computed(() => note ? ({
-		userId: note.userId,
-		sinceId: note.id,
+	params: computed(() => note.value ? ({
+		userId: note.value.userId,
+		sinceId: note.value.id,
 	}) : null),
 };
 
 function fetchNote() {
-	showPrev = false;
-	showNext = false;
-	note = null;
+	showPrev.value = false;
+	showNext.value = false;
+	note.value = null;
 	os.api('notes/show', {
 		noteId: props.noteId,
 	}).then(res => {
-		note = res;
+		note.value = res;
 		// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
-		if (note.clippedCount > 0 || new Date(note.createdAt).getTime() < new Date('2023-10-01').getTime()) {
+		if (note.value.clippedCount > 0 || new Date(note.value.createdAt).getTime() < new Date('2023-10-01').getTime()) {
 			os.api('notes/clips', {
-				noteId: note.id,
+				noteId: note.value.id,
 			}).then((_clips) => {
-				clips = _clips;
+				clips.value = _clips;
 			});
 		}
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
@@ -111,18 +111,18 @@ watch(() => props.noteId, fetchNote, {
 	immediate: true,
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => note ? {
+definePageMetadata(computed(() => note.value ? {
 	title: i18n.ts.note,
-	subtitle: dateString(note.createdAt),
-	avatar: note.user,
-	path: `/notes/${note.id}`,
+	subtitle: dateString(note.value.createdAt),
+	avatar: note.value.user,
+	path: `/notes/${note.value.id}`,
 	share: {
-		title: i18n.t('noteOf', { user: note.user.name }),
-		text: note.text,
+		title: i18n.t('noteOf', { user: note.value.user.name }),
+		text: note.value.text,
 	},
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index 8d2475b085..71ce7c353b 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import XNotifications from '@/components/MkNotifications.vue';
 import MkNotes from '@/components/MkNotes.vue';
 import * as os from '@/os.js';
@@ -29,9 +29,9 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { notificationTypes } from '@/const.js';
 
-let tab = $ref('all');
-let includeTypes = $ref<string[] | null>(null);
-const excludeTypes = $computed(() => includeTypes ? notificationTypes.filter(t => !includeTypes.includes(t)) : null);
+const tab = ref('all');
+const includeTypes = ref<string[] | null>(null);
+const excludeTypes = computed(() => includeTypes.value ? notificationTypes.filter(t => !includeTypes.value.includes(t)) : null);
 
 const mentionsPagination = {
 	endpoint: 'notes/mentions' as const,
@@ -49,27 +49,27 @@ const directNotesPagination = {
 function setFilter(ev) {
 	const typeItems = notificationTypes.map(t => ({
 		text: i18n.t(`_notification._types.${t}`),
-		active: includeTypes && includeTypes.includes(t),
+		active: includeTypes.value && includeTypes.value.includes(t),
 		action: () => {
-			includeTypes = [t];
+			includeTypes.value = [t];
 		},
 	}));
-	const items = includeTypes != null ? [{
+	const items = includeTypes.value != null ? [{
 		icon: 'ti ti-x',
 		text: i18n.ts.clear,
 		action: () => {
-			includeTypes = null;
+			includeTypes.value = null;
 		},
 	}, null, ...typeItems] : typeItems;
 	os.popupMenu(items, ev.currentTarget ?? ev.target);
 }
 
-const headerActions = $computed(() => [tab === 'all' ? {
+const headerActions = computed(() => [tab.value === 'all' ? {
 	text: i18n.ts.filter,
 	icon: 'ti ti-filter',
-	highlighted: includeTypes != null,
+	highlighted: includeTypes.value != null,
 	handler: setFilter,
-} : undefined, tab === 'all' ? {
+} : undefined, tab.value === 'all' ? {
 	text: i18n.ts.markAllAsRead,
 	icon: 'ti ti-check',
 	handler: () => {
@@ -77,7 +77,7 @@ const headerActions = $computed(() => [tab === 'all' ? {
 	},
 } : undefined].filter(x => x !== undefined));
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'all',
 	title: i18n.ts.all,
 	icon: 'ti ti-point',
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 4fffd311b2..e3f116dc6c 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
@@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 /* eslint-disable vue/no-mutating-props */
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import XContainer from '../page-editor.container.vue';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import * as os from '@/os.js';
@@ -35,14 +35,14 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', value: any): void;
 }>();
 
-let file: any = $ref(null);
+const file = ref<any>(null);
 
 async function choose() {
 	os.selectDriveFile(false).then((fileResponse) => {
-		file = fileResponse[0];
+		file.value = fileResponse[0];
 		emit('update:modelValue', {
 			...props.modelValue,
-			fileId: file.id,
+			fileId: file.value.id,
 		});
 	});
 }
@@ -54,7 +54,7 @@ onMounted(async () => {
 		os.api('drive/files/show', {
 			fileId: props.modelValue.fileId,
 		}).then(fileResponse => {
-			file = fileResponse;
+			file.value = fileResponse;
 		});
 	}
 });
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
index fc436aad75..ce3980ac8d 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 /* eslint-disable vue/no-mutating-props */
-import { watch } from 'vue';
+import { watch, ref } from 'vue';
 import XContainer from '../page-editor.container.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -40,19 +40,19 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', value: any): void;
 }>();
 
-let id: any = $ref(props.modelValue.note);
-let note: any = $ref(null);
+const id = ref<any>(props.modelValue.note);
+const note = ref<any>(null);
 
-watch($$(id), async () => {
-	if (id && (id.startsWith('http://') || id.startsWith('https://'))) {
-		id = (id.endsWith('/') ? id.slice(0, -1) : id).split('/').pop();
+watch(id, async () => {
+	if (id.value && (id.value.startsWith('http://') || id.value.startsWith('https://'))) {
+		id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop();
 	}
 
 	emit('update:modelValue', {
 		...props.modelValue,
-		note: id,
+		note: id.value,
 	});
-	note = await os.api('notes/show', { noteId: id });
+	note.value = await os.api('notes/show', { noteId: id.value });
 }, {
 	immediate: true,
 });
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
index 31c2c96d7c..1220ca29a7 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 /* eslint-disable vue/no-mutating-props */
-import { defineAsyncComponent, inject, onMounted, watch } from 'vue';
+import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XContainer from '../page-editor.container.vue';
 import * as os from '@/os.js';
@@ -42,12 +42,12 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', value: any): void;
 }>();
 
-const children = $ref(deepClone(props.modelValue.children ?? []));
+const children = ref(deepClone(props.modelValue.children ?? []));
 
-watch($$(children), () => {
+watch(children, () => {
 	emit('update:modelValue', {
 		...props.modelValue,
-		children,
+		children: children.value,
 	});
 }, {
 	deep: true,
@@ -75,7 +75,7 @@ async function add() {
 	if (canceled) return;
 
 	const id = uuid();
-	children.push({ id, type });
+	children.value.push({ id, type });
 }
 
 onMounted(() => {
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
index 2ecf5790b8..4f47a77bdd 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 /* eslint-disable vue/no-mutating-props */
-import { watch } from 'vue';
+import { watch, ref } from 'vue';
 import XContainer from '../page-editor.container.vue';
 import { i18n } from '@/i18n.js';
 
@@ -28,12 +28,12 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', value: any): void;
 }>();
 
-const text = $ref(props.modelValue.text ?? '');
+const text = ref(props.modelValue.text ?? '');
 
-watch($$(text), () => {
+watch(text, () => {
 	emit('update:modelValue', {
 		...props.modelValue,
-		text,
+		text: text.value,
 	});
 });
 </script>
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index dc749c292e..e95dd1f39e 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, provide, watch } from 'vue';
+import { computed, provide, watch, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XBlocks from './page-editor.blocks.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -82,47 +82,47 @@ const props = defineProps<{
 	initUser?: string;
 }>();
 
-let tab = $ref('settings');
-let author = $ref($i);
-let readonly = $ref(false);
-let page = $ref(null);
-let pageId = $ref(null);
-let currentName = $ref(null);
-let title = $ref('');
-let summary = $ref(null);
-let name = $ref(Date.now().toString());
-let eyeCatchingImage = $ref(null);
-let eyeCatchingImageId = $ref(null);
-let font = $ref('sans-serif');
-let content = $ref([]);
-let alignCenter = $ref(false);
-let hideTitleWhenPinned = $ref(false);
+const tab = ref('settings');
+const author = ref($i);
+const readonly = ref(false);
+const page = ref(null);
+const pageId = ref(null);
+const currentName = ref(null);
+const title = ref('');
+const summary = ref(null);
+const name = ref(Date.now().toString());
+const eyeCatchingImage = ref(null);
+const eyeCatchingImageId = ref(null);
+const font = ref('sans-serif');
+const content = ref([]);
+const alignCenter = ref(false);
+const hideTitleWhenPinned = ref(false);
 
-provide('readonly', readonly);
+provide('readonly', readonly.value);
 provide('getPageBlockList', getPageBlockList);
 
-watch($$(eyeCatchingImageId), async () => {
-	if (eyeCatchingImageId == null) {
-		eyeCatchingImage = null;
+watch(eyeCatchingImageId, async () => {
+	if (eyeCatchingImageId.value == null) {
+		eyeCatchingImage.value = null;
 	} else {
-		eyeCatchingImage = await os.api('drive/files/show', {
-			fileId: eyeCatchingImageId,
+		eyeCatchingImage.value = await os.api('drive/files/show', {
+			fileId: eyeCatchingImageId.value,
 		});
 	}
 });
 
 function getSaveOptions() {
 	return {
-		title: title.trim(),
-		name: name.trim(),
-		summary: summary,
-		font: font,
+		title: title.value.trim(),
+		name: name.value.trim(),
+		summary: summary.value,
+		font: font.value,
 		script: '',
-		hideTitleWhenPinned: hideTitleWhenPinned,
-		alignCenter: alignCenter,
-		content: content,
+		hideTitleWhenPinned: hideTitleWhenPinned.value,
+		alignCenter: alignCenter.value,
+		content: content.value,
 		variables: [],
-		eyeCatchingImageId: eyeCatchingImageId,
+		eyeCatchingImageId: eyeCatchingImageId.value,
 	};
 }
 
@@ -146,11 +146,11 @@ function save() {
 		}
 	};
 
-	if (pageId) {
-		options.pageId = pageId;
+	if (pageId.value) {
+		options.pageId = pageId.value;
 		os.api('pages/update', options)
 			.then(page => {
-				currentName = name.trim();
+				currentName.value = name.value.trim();
 				os.alert({
 					type: 'success',
 					text: i18n.ts._pages.updated,
@@ -159,13 +159,13 @@ function save() {
 	} else {
 		os.api('pages/create', options)
 			.then(created => {
-				pageId = created.id;
-				currentName = name.trim();
+				pageId.value = created.id;
+				currentName.value = name.value.trim();
 				os.alert({
 					type: 'success',
 					text: i18n.ts._pages.created,
 				});
-				mainRouter.push(`/pages/edit/${pageId}`);
+				mainRouter.push(`/pages/edit/${pageId.value}`);
 			}).catch(onError);
 	}
 }
@@ -173,11 +173,11 @@ function save() {
 function del() {
 	os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: title.trim() }),
+		text: i18n.t('removeAreYouSure', { x: title.value.trim() }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
 		os.api('pages/delete', {
-			pageId: pageId,
+			pageId: pageId.value,
 		}).then(() => {
 			os.alert({
 				type: 'success',
@@ -189,16 +189,16 @@ function del() {
 }
 
 function duplicate() {
-	title = title + ' - copy';
-	name = name + '-copy';
+	title.value = title.value + ' - copy';
+	name.value = name.value + '-copy';
 	os.api('pages/create', getSaveOptions()).then(created => {
-		pageId = created.id;
-		currentName = name.trim();
+		pageId.value = created.id;
+		currentName.value = name.value.trim();
 		os.alert({
 			type: 'success',
 			text: i18n.ts._pages.created,
 		});
-		mainRouter.push(`/pages/edit/${pageId}`);
+		mainRouter.push(`/pages/edit/${pageId.value}`);
 	});
 }
 
@@ -211,7 +211,7 @@ async function add() {
 	if (canceled) return;
 
 	const id = uuid();
-	content.push({ id, type });
+	content.value.push({ id, type });
 }
 
 function getPageBlockList() {
@@ -225,42 +225,42 @@ function getPageBlockList() {
 
 function setEyeCatchingImage(img) {
 	selectFile(img.currentTarget ?? img.target, null).then(file => {
-		eyeCatchingImageId = file.id;
+		eyeCatchingImageId.value = file.id;
 	});
 }
 
 function removeEyeCatchingImage() {
-	eyeCatchingImageId = null;
+	eyeCatchingImageId.value = null;
 }
 
 async function init() {
 	if (props.initPageId) {
-		page = await os.api('pages/show', {
+		page.value = await os.api('pages/show', {
 			pageId: props.initPageId,
 		});
 	} else if (props.initPageName && props.initUser) {
-		page = await os.api('pages/show', {
+		page.value = await os.api('pages/show', {
 			name: props.initPageName,
 			username: props.initUser,
 		});
-		readonly = true;
+		readonly.value = true;
 	}
 
-	if (page) {
-		author = page.user;
-		pageId = page.id;
-		title = page.title;
-		name = page.name;
-		currentName = page.name;
-		summary = page.summary;
-		font = page.font;
-		hideTitleWhenPinned = page.hideTitleWhenPinned;
-		alignCenter = page.alignCenter;
-		content = page.content;
-		eyeCatchingImageId = page.eyeCatchingImageId;
+	if (page.value) {
+		author.value = page.value.user;
+		pageId.value = page.value.id;
+		title.value = page.value.title;
+		name.value = page.value.name;
+		currentName.value = page.value.name;
+		summary.value = page.value.summary;
+		font.value = page.value.font;
+		hideTitleWhenPinned.value = page.value.hideTitleWhenPinned;
+		alignCenter.value = page.value.alignCenter;
+		content.value = page.value.content;
+		eyeCatchingImageId.value = page.value.eyeCatchingImageId;
 	} else {
 		const id = uuid();
-		content = [{
+		content.value = [{
 			id,
 			type: 'text',
 			text: 'Hello World!',
@@ -270,9 +270,9 @@ async function init() {
 
 init();
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'settings',
 	title: i18n.ts._pages.pageSetting,
 	icon: 'ti ti-settings',
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 2bc053ccfe..a342dff41f 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -76,7 +76,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import XPage from '@/components/page/page.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
@@ -99,24 +99,24 @@ const props = defineProps<{
 	username: string;
 }>();
 
-let page = $ref(null);
-let error = $ref(null);
+const page = ref(null);
+const error = ref(null);
 const otherPostsPagination = {
 	endpoint: 'users/pages' as const,
 	limit: 6,
 	params: computed(() => ({
-		userId: page.user.id,
+		userId: page.value.user.id,
 	})),
 };
-const path = $computed(() => props.username + '/' + props.pageName);
+const path = computed(() => props.username + '/' + props.pageName);
 
 function fetchPage() {
-	page = null;
+	page.value = null;
 	os.api('pages/show', {
 		name: props.pageName,
 		username: props.username,
 	}).then(async _page => {
-		page = _page;
+		page.value = _page;
 
 		// plugin
 		if (pageViewInterruptors.length > 0) {
@@ -124,38 +124,38 @@ function fetchPage() {
 			for (const interruptor of pageViewInterruptors) {
 				result = await interruptor.handler(result);
 			}
-			page = result;
+			page.value = result;
 		}
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
 function share() {
 	navigator.share({
-		title: page.title ?? page.name,
-		text: page.summary,
-		url: `${url}/@${page.user.username}/pages/${page.name}`,
+		title: page.value.title ?? page.value.name,
+		text: page.value.summary,
+		url: `${url}/@${page.value.user.username}/pages/${page.value.name}`,
 	});
 }
 
 function copyLink() {
-	copyToClipboard(`${url}/@${page.user.username}/pages/${page.name}`);
+	copyToClipboard(`${url}/@${page.value.user.username}/pages/${page.value.name}`);
 	os.success();
 }
 
 function shareWithNote() {
 	os.post({
-		initialText: `${page.title || page.name} ${url}/@${page.user.username}/pages/${page.name}`,
+		initialText: `${page.value.title || page.value.name} ${url}/@${page.value.user.username}/pages/${page.value.name}`,
 	});
 }
 
 function like() {
 	os.apiWithDialog('pages/like', {
-		pageId: page.id,
+		pageId: page.value.id,
 	}).then(() => {
-		page.isLiked = true;
-		page.likedCount++;
+		page.value.isLiked = true;
+		page.value.likedCount++;
 	});
 }
 
@@ -166,32 +166,32 @@ async function unlike() {
 	});
 	if (confirm.canceled) return;
 	os.apiWithDialog('pages/unlike', {
-		pageId: page.id,
+		pageId: page.value.id,
 	}).then(() => {
-		page.isLiked = false;
-		page.likedCount--;
+		page.value.isLiked = false;
+		page.value.likedCount--;
 	});
 }
 
 function pin(pin) {
 	os.apiWithDialog('i/update', {
-		pinnedPageId: pin ? page.id : null,
+		pinnedPageId: pin ? page.value.id : null,
 	});
 }
 
-watch(() => path, fetchPage, { immediate: true });
+watch(() => path.value, fetchPage, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => page ? {
-	title: page.title || page.name,
-	avatar: page.user,
-	path: `/@${page.user.username}/pages/${page.name}`,
+definePageMetadata(computed(() => page.value ? {
+	title: page.value.title || page.value.name,
+	avatar: page.value.user,
+	path: `/@${page.value.user.username}/pages/${page.value.name}`,
 	share: {
-		title: page.title || page.name,
-		text: page.summary,
+		title: page.value.title || page.value.name,
+		text: page.value.summary,
 	},
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index 6f40b24771..bc51b55c7f 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import MkPagePreview from '@/components/MkPagePreview.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -46,7 +46,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const router = useRouter();
 
-let tab = $ref('featured');
+const tab = ref('featured');
 
 const featuredPagesPagination = {
 	endpoint: 'pages/featured' as const,
@@ -65,13 +65,13 @@ function create() {
 	router.push('/pages/new');
 }
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	icon: 'ti ti-plus',
 	text: i18n.ts.create,
 	handler: create,
 }]);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'featured',
 	title: i18n.ts._pages.featured,
 	icon: 'ti ti-flare',
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index 387cb2f1f7..49a8642fc4 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, computed, ref } from 'vue';
 import JSON5 from 'json5';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -49,16 +49,16 @@ const props = defineProps<{
 	domain: string;
 }>();
 
-const scope = $computed(() => props.path ? props.path.split('/') : []);
+const scope = computed(() => props.path ? props.path.split('/') : []);
 
-let keys = $ref(null);
+const keys = ref(null);
 
 function fetchKeys() {
 	os.api('i/registry/keys-with-type', {
-		scope: scope,
+		scope: scope.value,
 		domain: props.domain === '@' ? null : props.domain,
 	}).then(res => {
-		keys = Object.entries(res).sort((a, b) => a[0].localeCompare(b[0]));
+		keys.value = Object.entries(res).sort((a, b) => a[0].localeCompare(b[0]));
 	});
 }
 
@@ -76,7 +76,7 @@ async function createKey() {
 		scope: {
 			type: 'string',
 			label: i18n.ts._registry.scope,
-			default: scope.join('/'),
+			default: scope.value.join('/'),
 		},
 	});
 	if (canceled) return;
@@ -91,9 +91,9 @@ async function createKey() {
 
 watch(() => props.path, fetchKeys, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.registry,
diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue
index 68d6c8c1a0..29406ec83c 100644
--- a/packages/frontend/src/pages/registry.value.vue
+++ b/packages/frontend/src/pages/registry.value.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, computed, ref } from 'vue';
 import JSON5 from 'json5';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -61,26 +61,26 @@ const props = defineProps<{
 	domain: string;
 }>();
 
-const scope = $computed(() => props.path.split('/').slice(0, -1));
-const key = $computed(() => props.path.split('/').at(-1));
+const scope = computed(() => props.path.split('/').slice(0, -1));
+const key = computed(() => props.path.split('/').at(-1));
 
-let value = $ref(null);
-let valueForEditor = $ref(null);
+const value = ref(null);
+const valueForEditor = ref(null);
 
 function fetchValue() {
 	os.api('i/registry/get-detail', {
-		scope,
-		key,
+		scope: scope.value,
+		key: key.value,
 		domain: props.domain === '@' ? null : props.domain,
 	}).then(res => {
-		value = res;
-		valueForEditor = JSON5.stringify(res.value, null, '\t');
+		value.value = res;
+		valueForEditor.value = JSON5.stringify(res.value, null, '\t');
 	});
 }
 
 async function save() {
 	try {
-		JSON5.parse(valueForEditor);
+		JSON5.parse(valueForEditor.value);
 	} catch (err) {
 		os.alert({
 			type: 'error',
@@ -94,9 +94,9 @@ async function save() {
 	}).then(({ canceled }) => {
 		if (canceled) return;
 		os.apiWithDialog('i/registry/set', {
-			scope,
-			key,
-			value: JSON5.parse(valueForEditor),
+			scope: scope.value,
+			key: key.value,
+			value: JSON5.parse(valueForEditor.value),
 			domain: props.domain === '@' ? null : props.domain,
 		});
 	});
@@ -109,8 +109,8 @@ function del() {
 	}).then(({ canceled }) => {
 		if (canceled) return;
 		os.apiWithDialog('i/registry/remove', {
-			scope,
-			key,
+			scope: scope.value,
+			key: key.value,
 			domain: props.domain === '@' ? null : props.domain,
 		});
 	});
@@ -118,9 +118,9 @@ function del() {
 
 watch(() => props.path, fetchValue, { immediate: true });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.registry,
diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue
index d0a3df5deb..e8bd006373 100644
--- a/packages/frontend/src/pages/registry.vue
+++ b/packages/frontend/src/pages/registry.vue
@@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref, computed } from 'vue';
 import JSON5 from 'json5';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -30,11 +31,11 @@ import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
 
-let scopesWithDomain = $ref(null);
+const scopesWithDomain = ref(null);
 
 function fetchScopes() {
 	os.api('i/registry/scopes-with-domain').then(res => {
-		scopesWithDomain = res;
+		scopesWithDomain.value = res;
 	});
 }
 
@@ -66,9 +67,9 @@ async function createKey() {
 
 fetchScopes();
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.registry,
diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue
index 718ca7d773..c9d193b787 100644
--- a/packages/frontend/src/pages/reset-password.vue
+++ b/packages/frontend/src/pages/reset-password.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, onMounted } from 'vue';
+import { defineAsyncComponent, onMounted, ref, computed } from 'vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
@@ -32,12 +32,12 @@ const props = defineProps<{
 	token?: string;
 }>();
 
-let password = $ref('');
+const password = ref('');
 
 async function save() {
 	await os.apiWithDialog('reset-password', {
 		token: props.token,
-		password: password,
+		password: password.value,
 	});
 	mainRouter.push('/');
 }
@@ -49,9 +49,9 @@ onMounted(() => {
 	}
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.resetPassword,
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index 1e3db42758..7d8785218f 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import * as os from '@/os.js';
 import MkUserList from '@/components/MkUserList.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -52,29 +52,29 @@ const props = withDefaults(defineProps<{
 	initialTab: 'users',
 });
 
-let tab = $ref(props.initialTab);
-let role = $ref();
-let error = $ref();
-let visible = $ref(false);
+const tab = ref(props.initialTab);
+const role = ref();
+const error = ref();
+const visible = ref(false);
 
 watch(() => props.role, () => {
 	os.api('roles/show', {
 		roleId: props.role,
 	}).then(res => {
-		role = res;
-		document.title = `${role?.name} | ${instanceName}`;
-		visible = res.isExplorable && res.isPublic;
+		role.value = res;
+		document.title = `${role.value?.name} | ${instanceName}`;
+		visible.value = res.isExplorable && res.isPublic;
 	}).catch((err) => {
 		if (err.code === 'NO_SUCH_ROLE') {
-			error = i18n.ts.noRole;
+			error.value = i18n.ts.noRole;
 		} else {
-			error = i18n.ts.somethingHappened;
+			error.value = i18n.ts.somethingHappened;
 		}
-		document.title = `${error} | ${instanceName}`;
+		document.title = `${error.value} | ${instanceName}`;
 	});
 }, { immediate: true });
 
-const users = $computed(() => ({
+const users = computed(() => ({
 	endpoint: 'roles/users' as const,
 	limit: 30,
 	params: {
@@ -82,7 +82,7 @@ const users = $computed(() => ({
 	},
 }));
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'users',
 	icon: 'ti ti-users',
 	title: i18n.ts.users,
@@ -93,7 +93,7 @@ const headerTabs = $computed(() => [{
 }]);
 
 definePageMetadata(computed(() => ({
-	title: role?.name,
+	title: role.value?.name,
 	icon: 'ti ti-badge',
 })));
 </script>
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index f8d3187bd4..1453bc1658 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onDeactivated, onUnmounted, Ref, ref, watch } from 'vue';
+import { onDeactivated, onUnmounted, Ref, ref, watch, computed } from 'vue';
 import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import MkContainer from '@/components/MkContainer.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -59,8 +59,8 @@ let aiscript: Interpreter;
 const code = ref('');
 const logs = ref<any[]>([]);
 const root = ref<AsUiRoot>();
-let components: Ref<AsUiComponent>[] = $ref([]);
-let uiKey = $ref(0);
+const components = ref<Ref<AsUiComponent>[]>([]);
+const uiKey = ref(0);
 
 const saved = miLocalStorage.getItem('scratchpad');
 if (saved) {
@@ -74,15 +74,15 @@ watch(code, () => {
 async function run() {
 	if (aiscript) aiscript.abort();
 	root.value = undefined;
-	components = [];
-	uiKey++;
+	components.value = [];
+	uiKey.value++;
 	logs.value = [];
 	aiscript = new Interpreter(({
 		...createAiScriptEnv({
 			storageKey: 'widget',
 			token: $i?.token,
 		}),
-		...registerAsUiLib(components, (_root) => {
+		...registerAsUiLib(components.value, (_root) => {
 			root.value = _root.value;
 		}),
 	}), {
@@ -160,9 +160,9 @@ onUnmounted(() => {
 	if (aiscript) aiscript.abort();
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.scratchpad,
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index c1692d8be2..3e74a6f591 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 import MkNotes from '@/components/MkNotes.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkRadios from '@/components/MkRadios.vue';
@@ -59,21 +59,21 @@ import MkFolder from '@/components/MkFolder.vue';
 
 const router = useRouter();
 
-let key = $ref(0);
-let searchQuery = $ref('');
-let searchOrigin = $ref('combined');
-let notePagination = $ref();
-let user = $ref(null);
-let isLocalOnly = $ref(false);
+const key = ref(0);
+const searchQuery = ref('');
+const searchOrigin = ref('combined');
+const notePagination = ref();
+const user = ref(null);
+const isLocalOnly = ref(false);
 
 function selectUser() {
 	os.selectUser().then(_user => {
-		user = _user;
+		user.value = _user;
 	});
 }
 
 async function search() {
-	const query = searchQuery.toString().trim();
+	const query = searchQuery.value.toString().trim();
 
 	if (query == null || query === '') return;
 
@@ -95,17 +95,17 @@ async function search() {
 		return;
 	}
 
-	notePagination = {
+	notePagination.value = {
 		endpoint: 'notes/search',
 		limit: 10,
 		params: {
-			query: searchQuery,
-			userId: user ? user.id : null,
+			query: searchQuery.value,
+			userId: user.value ? user.value.id : null,
 		},
 	};
 
-	if (isLocalOnly) notePagination.params.host = '.';
+	if (isLocalOnly.value) notePagination.value.params.host = '.';
 
-	key++;
+	key.value++;
 }
 </script>
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index 1a7501adc6..39707e634c 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted } from 'vue';
+import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
 import MkUserList from '@/components/MkUserList.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkRadios from '@/components/MkRadios.vue';
@@ -40,13 +40,13 @@ import { useRouter } from '@/router.js';
 
 const router = useRouter();
 
-let key = $ref('');
-let searchQuery = $ref('');
-let searchOrigin = $ref('combined');
-let userPagination = $ref();
+const key = ref('');
+const searchQuery = ref('');
+const searchOrigin = ref('combined');
+const userPagination = ref();
 
 async function search() {
-	const query = searchQuery.toString().trim();
+	const query = searchQuery.value.toString().trim();
 
 	if (query == null || query === '') return;
 
@@ -68,15 +68,15 @@ async function search() {
 		return;
 	}
 
-	userPagination = {
+	userPagination.value = {
 		endpoint: 'users/search',
 		limit: 10,
 		params: {
 			query: query,
-			origin: searchOrigin,
+			origin: searchOrigin.value,
 		},
 	};
 
-	key = query;
+	key.value = query;
 }
 </script>
diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index e205fe850f..c47414e573 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted } from 'vue';
+import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import * as os from '@/os.js';
@@ -34,13 +34,13 @@ import MkInfo from '@/components/MkInfo.vue';
 const XNote = defineAsyncComponent(() => import('./search.note.vue'));
 const XUser = defineAsyncComponent(() => import('./search.user.vue'));
 
-let tab = $ref('note');
+const tab = ref('note');
 
 const notesSearchAvailable = (($i == null && instance.policies.canSearchNotes) || ($i != null && $i.policies.canSearchNotes));
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => [{
+const headerTabs = computed(() => [{
 	key: 'note',
 	title: i18n.ts.notes,
 	icon: 'ti ti-pencil',
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index 8a89a3a86d..d9a59cdc35 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, defineAsyncComponent } from 'vue';
+import { ref, defineAsyncComponent, computed } from 'vue';
 import { supported as webAuthnSupported, create as webAuthnCreate, parseCreationOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill';
 import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -91,7 +91,7 @@ withDefaults(defineProps<{
 	first: false,
 });
 
-const usePasswordLessLogin = $computed(() => $i?.usePasswordLessLogin ?? false);
+const usePasswordLessLogin = computed(() => $i?.usePasswordLessLogin ?? false);
 
 async function registerTOTP(): Promise<void> {
 	const auth = await os.authenticateDialog();
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index df8c7b440a..6ed04ecf9e 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, ref } from 'vue';
+import { defineAsyncComponent, ref, computed } from 'vue';
 import type * as Misskey from 'misskey-js';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -101,9 +101,9 @@ function switchAccountWithToken(token: string) {
 	login(token);
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.accounts,
diff --git a/packages/frontend/src/pages/settings/api.vue b/packages/frontend/src/pages/settings/api.vue
index e0266bccba..eee7884aaa 100644
--- a/packages/frontend/src/pages/settings/api.vue
+++ b/packages/frontend/src/pages/settings/api.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, ref } from 'vue';
+import { defineAsyncComponent, ref, computed } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
@@ -40,9 +40,9 @@ function generateToken() {
 	}, 'closed');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: 'API',
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index 7fd4ed61c9..f461271f0b 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import FormPagination from '@/components/MkPagination.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -71,9 +71,9 @@ function revoke(token) {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.installedApps,
diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue
index d58f959fc5..e33e778246 100644
--- a/packages/frontend/src/pages/settings/custom-css.vue
+++ b/packages/frontend/src/pages/settings/custom-css.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, watch } from 'vue';
+import { ref, watch, computed } from 'vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import FormInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
@@ -41,9 +41,9 @@ watch(localCustomCss, async () => {
 	await apply();
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.customCss,
diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue
index 9fecc65d6d..b681e0d159 100644
--- a/packages/frontend/src/pages/settings/deck.vue
+++ b/packages/frontend/src/pages/settings/deck.vue
@@ -32,9 +32,9 @@ const useSimpleUiForNonRootPages = computed(deckStore.makeGetterSetter('useSimpl
 const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn'));
 const columnAlign = computed(deckStore.makeGetterSetter('columnAlign'));
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.deck,
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 899d824bf9..8da60ef504 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -60,7 +60,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkSelect from '@/components/MkSelect.vue';
 import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
 
-let sortMode = ref('+size');
+const sortMode = ref('+size');
 const pagination = {
 	endpoint: 'drive/files' as const,
 	limit: 10,
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index 01a0711682..8f5c313d16 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -76,8 +76,8 @@ const fetching = ref(true);
 const usage = ref<any>(null);
 const capacity = ref<any>(null);
 const uploadFolder = ref<any>(null);
-let alwaysMarkNsfw = $ref($i.alwaysMarkNsfw);
-let autoSensitive = $ref($i.autoSensitive);
+const alwaysMarkNsfw = ref($i.alwaysMarkNsfw);
+const autoSensitive = ref($i.autoSensitive);
 
 const meterStyle = computed(() => {
 	return {
@@ -122,21 +122,21 @@ function chooseUploadFolder() {
 
 function saveProfile() {
 	os.api('i/update', {
-		alwaysMarkNsfw: !!alwaysMarkNsfw,
-		autoSensitive: !!autoSensitive,
+		alwaysMarkNsfw: !!alwaysMarkNsfw.value,
+		autoSensitive: !!autoSensitive.value,
 	}).catch(err => {
 		os.alert({
 			type: 'error',
 			title: i18n.ts.error,
 			text: err.message,
 		});
-		alwaysMarkNsfw = true;
+		alwaysMarkNsfw.value = true;
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.drive,
diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue
index 82b7f0ae4c..309e025ada 100644
--- a/packages/frontend/src/pages/settings/email.vue
+++ b/packages/frontend/src/pages/settings/email.vue
@@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, watch } from 'vue';
+import { onMounted, ref, watch, computed } from 'vue';
 import FormSection from '@/components/form/section.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -106,9 +106,9 @@ onMounted(() => {
 	});
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.email,
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 717021abd0..f108a0c64e 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -427,9 +427,9 @@ watch(dataSaver, (to) => {
 	deep: true,
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.general,
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index 0f01fda26f..858983a214 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -111,7 +111,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -208,9 +208,9 @@ const importAntennas = async (ev) => {
 	os.api('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
 };
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.importAndExport,
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 5a1a9aedb3..49290e7c22 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -50,14 +50,14 @@ const childInfo = ref(null);
 
 const router = useRouter();
 
-let narrow = $ref(false);
+const narrow = ref(false);
 const NARROW_THRESHOLD = 600;
 
-let currentPage = $computed(() => router.currentRef.value.child);
+const currentPage = computed(() => router.currentRef.value.child);
 
 const ro = new ResizeObserver((entries, observer) => {
 	if (entries.length === 0) return;
-	narrow = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
+	narrow.value = entries[0].borderBoxSize[0].inlineSize < NARROW_THRESHOLD;
 });
 
 const menuDef = computed(() => [{
@@ -66,37 +66,37 @@ const menuDef = computed(() => [{
 		icon: 'ti ti-user',
 		text: i18n.ts.profile,
 		to: '/settings/profile',
-		active: currentPage?.route.name === 'profile',
+		active: currentPage.value?.route.name === 'profile',
 	}, {
 		icon: 'ti ti-lock-open',
 		text: i18n.ts.privacy,
 		to: '/settings/privacy',
-		active: currentPage?.route.name === 'privacy',
+		active: currentPage.value?.route.name === 'privacy',
 	}, {
 		icon: 'ti ti-mood-happy',
 		text: i18n.ts.reaction,
 		to: '/settings/reaction',
-		active: currentPage?.route.name === 'reaction',
+		active: currentPage.value?.route.name === 'reaction',
 	}, {
 		icon: 'ti ti-cloud',
 		text: i18n.ts.drive,
 		to: '/settings/drive',
-		active: currentPage?.route.name === 'drive',
+		active: currentPage.value?.route.name === 'drive',
 	}, {
 		icon: 'ti ti-bell',
 		text: i18n.ts.notifications,
 		to: '/settings/notifications',
-		active: currentPage?.route.name === 'notifications',
+		active: currentPage.value?.route.name === 'notifications',
 	}, {
 		icon: 'ti ti-mail',
 		text: i18n.ts.email,
 		to: '/settings/email',
-		active: currentPage?.route.name === 'email',
+		active: currentPage.value?.route.name === 'email',
 	}, {
 		icon: 'ti ti-lock',
 		text: i18n.ts.security,
 		to: '/settings/security',
-		active: currentPage?.route.name === 'security',
+		active: currentPage.value?.route.name === 'security',
 	}],
 }, {
 	title: i18n.ts.clientSettings,
@@ -104,32 +104,32 @@ const menuDef = computed(() => [{
 		icon: 'ti ti-adjustments',
 		text: i18n.ts.general,
 		to: '/settings/general',
-		active: currentPage?.route.name === 'general',
+		active: currentPage.value?.route.name === 'general',
 	}, {
 		icon: 'ti ti-palette',
 		text: i18n.ts.theme,
 		to: '/settings/theme',
-		active: currentPage?.route.name === 'theme',
+		active: currentPage.value?.route.name === 'theme',
 	}, {
 		icon: 'ti ti-menu-2',
 		text: i18n.ts.navbar,
 		to: '/settings/navbar',
-		active: currentPage?.route.name === 'navbar',
+		active: currentPage.value?.route.name === 'navbar',
 	}, {
 		icon: 'ti ti-equal-double',
 		text: i18n.ts.statusbar,
 		to: '/settings/statusbar',
-		active: currentPage?.route.name === 'statusbar',
+		active: currentPage.value?.route.name === 'statusbar',
 	}, {
 		icon: 'ti ti-music',
 		text: i18n.ts.sounds,
 		to: '/settings/sounds',
-		active: currentPage?.route.name === 'sounds',
+		active: currentPage.value?.route.name === 'sounds',
 	}, {
 		icon: 'ti ti-plug',
 		text: i18n.ts.plugins,
 		to: '/settings/plugin',
-		active: currentPage?.route.name === 'plugin',
+		active: currentPage.value?.route.name === 'plugin',
 	}],
 }, {
 	title: i18n.ts.otherSettings,
@@ -137,44 +137,44 @@ const menuDef = computed(() => [{
 		icon: 'ti ti-badges',
 		text: i18n.ts.roles,
 		to: '/settings/roles',
-		active: currentPage?.route.name === 'roles',
+		active: currentPage.value?.route.name === 'roles',
 	}, {
 		icon: 'ti ti-ban',
 		text: i18n.ts.muteAndBlock,
 		to: '/settings/mute-block',
-		active: currentPage?.route.name === 'mute-block',
+		active: currentPage.value?.route.name === 'mute-block',
 	}, {
 		icon: 'ti ti-api',
 		text: 'API',
 		to: '/settings/api',
-		active: currentPage?.route.name === 'api',
+		active: currentPage.value?.route.name === 'api',
 	}, {
 		icon: 'ti ti-webhook',
 		text: 'Webhook',
 		to: '/settings/webhook',
-		active: currentPage?.route.name === 'webhook',
+		active: currentPage.value?.route.name === 'webhook',
 	}, {
 		icon: 'ti ti-package',
 		text: i18n.ts.importAndExport,
 		to: '/settings/import-export',
-		active: currentPage?.route.name === 'import-export',
+		active: currentPage.value?.route.name === 'import-export',
 	}, {
 		icon: 'ti ti-plane',
 		text: `${i18n.ts.accountMigration}`,
 		to: '/settings/migration',
-		active: currentPage?.route.name === 'migration',
+		active: currentPage.value?.route.name === 'migration',
 	}, {
 		icon: 'ti ti-dots',
 		text: i18n.ts.other,
 		to: '/settings/other',
-		active: currentPage?.route.name === 'other',
+		active: currentPage.value?.route.name === 'other',
 	}],
 }, {
 	items: [{
 		icon: 'ti ti-device-floppy',
 		text: i18n.ts.preferencesBackups,
 		to: '/settings/preferences-backups',
-		active: currentPage?.route.name === 'preferences-backups',
+		active: currentPage.value?.route.name === 'preferences-backups',
 	}, {
 		type: 'button',
 		icon: 'ti ti-trash',
@@ -198,23 +198,23 @@ const menuDef = computed(() => [{
 	}],
 }]);
 
-watch($$(narrow), () => {
+watch(narrow, () => {
 });
 
 onMounted(() => {
 	ro.observe(el.value);
 
-	narrow = el.value.offsetWidth < NARROW_THRESHOLD;
+	narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
 
-	if (!narrow && currentPage?.route.name == null) {
+	if (!narrow.value && currentPage.value?.route.name == null) {
 		router.replace('/settings/profile');
 	}
 });
 
 onActivated(() => {
-	narrow = el.value.offsetWidth < NARROW_THRESHOLD;
+	narrow.value = el.value.offsetWidth < NARROW_THRESHOLD;
 
-	if (!narrow && currentPage?.route.name == null) {
+	if (!narrow.value && currentPage.value?.route.name == null) {
 		router.replace('/settings/profile');
 	}
 });
@@ -224,7 +224,7 @@ onUnmounted(() => {
 });
 
 watch(router.currentRef, (to) => {
-	if (to.route.name === 'settings' && to.child?.route.name == null && !narrow) {
+	if (to.route.name === 'settings' && to.child?.route.name == null && !narrow.value) {
 		router.replace('/settings/profile');
 	}
 });
@@ -239,9 +239,9 @@ provideMetadataReceiver((info) => {
 	}
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(INFO);
 // w 890
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index 4883ca0df4..83f7baf428 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import XInstanceMute from './mute-block.instance-mute.vue';
 import XWordMute from './mute-block.word-mute.vue';
 import MkPagination from '@/components/MkPagination.vue';
@@ -154,9 +154,9 @@ const blockingPagination = {
 	limit: 10,
 };
 
-let expandedRenoteMuteItems = $ref([]);
-let expandedMuteItems = $ref([]);
-let expandedBlockItems = $ref([]);
+const expandedRenoteMuteItems = ref([]);
+const expandedMuteItems = ref([]);
+const expandedBlockItems = ref([]);
 
 async function unrenoteMute(user, ev) {
 	os.popupMenu([{
@@ -192,26 +192,26 @@ async function unblock(user, ev) {
 }
 
 async function toggleRenoteMuteItem(item) {
-	if (expandedRenoteMuteItems.includes(item.id)) {
-		expandedRenoteMuteItems = expandedRenoteMuteItems.filter(x => x !== item.id);
+	if (expandedRenoteMuteItems.value.includes(item.id)) {
+		expandedRenoteMuteItems.value = expandedRenoteMuteItems.value.filter(x => x !== item.id);
 	} else {
-		expandedRenoteMuteItems.push(item.id);
+		expandedRenoteMuteItems.value.push(item.id);
 	}
 }
 
 async function toggleMuteItem(item) {
-	if (expandedMuteItems.includes(item.id)) {
-		expandedMuteItems = expandedMuteItems.filter(x => x !== item.id);
+	if (expandedMuteItems.value.includes(item.id)) {
+		expandedMuteItems.value = expandedMuteItems.value.filter(x => x !== item.id);
 	} else {
-		expandedMuteItems.push(item.id);
+		expandedMuteItems.value.push(item.id);
 	}
 }
 
 async function toggleBlockItem(item) {
-	if (expandedBlockItems.includes(item.id)) {
-		expandedBlockItems = expandedBlockItems.filter(x => x !== item.id);
+	if (expandedBlockItems.value.includes(item.id)) {
+		expandedBlockItems.value = expandedBlockItems.value.filter(x => x !== item.id);
 	} else {
-		expandedBlockItems.push(item.id);
+		expandedBlockItems.value.push(item.id);
 	}
 }
 
@@ -223,9 +223,9 @@ async function saveHardMutedWords(hardMutedWords: (string | string[])[]) {
 	await os.api('i/update', { hardMutedWords });
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.muteAndBlock,
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index c9cccd7dcb..66477a86ca 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -115,9 +115,9 @@ watch(menuDisplay, async () => {
 	await reloadAsk();
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.navbar,
diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue
index c1f107c2b8..5c8378e1dc 100644
--- a/packages/frontend/src/pages/settings/notifications.notification-config.vue
+++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkSelect from '@/components/MkSelect.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -41,10 +41,10 @@ const emit = defineEmits<{
 	(ev: 'update', result: any): void;
 }>();
 
-let type = $ref(props.value.type);
-let userListId = $ref(props.value.userListId);
+const type = ref(props.value.type);
+const userListId = ref(props.value.userListId);
 
 function save() {
-	emit('update', { type, userListId });
+	emit('update', { type: type.value, userListId: userListId.value });
 }
 </script>
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index 7b09c6c900..394e428eda 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -55,7 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, shallowRef, computed } from 'vue';
 import XNotificationConfig from './notifications.notification-config.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
@@ -70,9 +70,9 @@ import { notificationTypes } from '@/const.js';
 
 const nonConfigurableNotificationTypes = ['note'];
 
-let allowButton = $shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
-let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer);
-let sendReadMessage = $computed(() => pushRegistrationInServer?.sendReadMessage || false);
+const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
+const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
+const sendReadMessage = computed(() => pushRegistrationInServer.value?.sendReadMessage || false);
 const userLists = await os.api('users/lists/list');
 
 async function readAllUnreadNotes() {
@@ -95,14 +95,14 @@ async function updateReceiveConfig(type, value) {
 }
 
 function onChangeSendReadMessage(v: boolean) {
-	if (!pushRegistrationInServer) return;
+	if (!pushRegistrationInServer.value) return;
 
 	os.apiWithDialog('sw/update-registration', {
-		endpoint: pushRegistrationInServer.endpoint,
+		endpoint: pushRegistrationInServer.value.endpoint,
 		sendReadMessage: v,
 	}).then(res => {
-		if (!allowButton)	return;
-		allowButton.pushRegistrationInServer = res;
+		if (!allowButton.value)	return;
+		allowButton.value.pushRegistrationInServer = res;
 	});
 }
 
@@ -110,9 +110,9 @@ function testNotification(): void {
 	os.api('notifications/test-notification');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.notifications,
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index a921e0cea9..340a9550b4 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -163,9 +163,9 @@ watch([
 	await reloadAsk();
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.other,
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue
index 693e02d0ed..f304d777a5 100644
--- a/packages/frontend/src/pages/settings/plugin.install.vue
+++ b/packages/frontend/src/pages/settings/plugin.install.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, ref } from 'vue';
+import { nextTick, ref, computed } from 'vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkButton from '@/components/MkButton.vue';
 import FormInfo from '@/components/MkInfo.vue';
@@ -49,9 +49,9 @@ async function install() {
 	}
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts._plugin.install,
diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue
index 5ebd74ef7a..bf760e623f 100644
--- a/packages/frontend/src/pages/settings/plugin.vue
+++ b/packages/frontend/src/pages/settings/plugin.vue
@@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, ref } from 'vue';
+import { nextTick, ref, computed } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import FormSection from '@/components/form/section.vue';
@@ -121,9 +121,9 @@ function changeActive(plugin, active) {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.plugins,
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index d195b4142c..971881ea24 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSection from '@/components/form/section.vue';
@@ -77,36 +77,36 @@ import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let isLocked = $ref($i.isLocked);
-let autoAcceptFollowed = $ref($i.autoAcceptFollowed);
-let noCrawle = $ref($i.noCrawle);
-let preventAiLearning = $ref($i.preventAiLearning);
-let isExplorable = $ref($i.isExplorable);
-let hideOnlineStatus = $ref($i.hideOnlineStatus);
-let publicReactions = $ref($i.publicReactions);
-let ffVisibility = $ref($i.ffVisibility);
+const isLocked = ref($i.isLocked);
+const autoAcceptFollowed = ref($i.autoAcceptFollowed);
+const noCrawle = ref($i.noCrawle);
+const preventAiLearning = ref($i.preventAiLearning);
+const isExplorable = ref($i.isExplorable);
+const hideOnlineStatus = ref($i.hideOnlineStatus);
+const publicReactions = ref($i.publicReactions);
+const ffVisibility = ref($i.ffVisibility);
 
-let defaultNoteVisibility = $computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
-let defaultNoteLocalOnly = $computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
-let rememberNoteVisibility = $computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
-let keepCw = $computed(defaultStore.makeGetterSetter('keepCw'));
+const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
+const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
+const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility'));
+const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
 
 function save() {
 	os.api('i/update', {
-		isLocked: !!isLocked,
-		autoAcceptFollowed: !!autoAcceptFollowed,
-		noCrawle: !!noCrawle,
-		preventAiLearning: !!preventAiLearning,
-		isExplorable: !!isExplorable,
-		hideOnlineStatus: !!hideOnlineStatus,
-		publicReactions: !!publicReactions,
-		ffVisibility: ffVisibility,
+		isLocked: !!isLocked.value,
+		autoAcceptFollowed: !!autoAcceptFollowed.value,
+		noCrawle: !!noCrawle.value,
+		preventAiLearning: !!preventAiLearning.value,
+		isExplorable: !!isExplorable.value,
+		hideOnlineStatus: !!hideOnlineStatus.value,
+		publicReactions: !!publicReactions.value,
+		ffVisibility: ffVisibility.value,
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.privacy,
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index f6e387da52..ba75b539e1 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -144,7 +144,7 @@ import MkInfo from '@/components/MkInfo.vue';
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
 const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
-let avatarDecorations: any[] = $ref([]);
+const avatarDecorations = ref<any[]>([]);
 
 const profile = reactive({
 	name: $i.name,
@@ -166,7 +166,7 @@ const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name
 const fieldEditMode = ref(false);
 
 os.api('get-avatar-decorations').then(_avatarDecorations => {
-	avatarDecorations = _avatarDecorations;
+	avatarDecorations.value = _avatarDecorations;
 });
 
 function addField() {
@@ -273,9 +273,9 @@ function openDecoration(avatarDecoration) {
 	}, {}, 'closed');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.profile,
diff --git a/packages/frontend/src/pages/settings/reaction.vue b/packages/frontend/src/pages/settings/reaction.vue
index fb0f975212..fe5d9fc443 100644
--- a/packages/frontend/src/pages/settings/reaction.vue
+++ b/packages/frontend/src/pages/settings/reaction.vue
@@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, watch } from 'vue';
+import { defineAsyncComponent, watch, ref, computed } from 'vue';
 import Sortable from 'vuedraggable';
 import MkRadios from '@/components/MkRadios.vue';
 import FromSlot from '@/components/form/slot.vue';
@@ -73,22 +73,22 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { deepClone } from '@/scripts/clone.js';
 
-let reactions = $ref(deepClone(defaultStore.state.reactions));
+const reactions = ref(deepClone(defaultStore.state.reactions));
 
-const reactionPickerSize = $computed(defaultStore.makeGetterSetter('reactionPickerSize'));
-const reactionPickerWidth = $computed(defaultStore.makeGetterSetter('reactionPickerWidth'));
-const reactionPickerHeight = $computed(defaultStore.makeGetterSetter('reactionPickerHeight'));
-const reactionPickerUseDrawerForMobile = $computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile'));
+const reactionPickerSize = computed(defaultStore.makeGetterSetter('reactionPickerSize'));
+const reactionPickerWidth = computed(defaultStore.makeGetterSetter('reactionPickerWidth'));
+const reactionPickerHeight = computed(defaultStore.makeGetterSetter('reactionPickerHeight'));
+const reactionPickerUseDrawerForMobile = computed(defaultStore.makeGetterSetter('reactionPickerUseDrawerForMobile'));
 
 function save() {
-	defaultStore.set('reactions', reactions);
+	defaultStore.set('reactions', reactions.value);
 }
 
 function remove(reaction, ev: MouseEvent) {
 	os.popupMenu([{
 		text: i18n.ts.remove,
 		action: () => {
-			reactions = reactions.filter(x => x !== reaction);
+			reactions.value = reactions.value.filter(x => x !== reaction);
 		},
 	}], ev.currentTarget ?? ev.target);
 }
@@ -107,28 +107,28 @@ async function setDefault() {
 	});
 	if (canceled) return;
 
-	reactions = deepClone(defaultStore.def.reactions.default);
+	reactions.value = deepClone(defaultStore.def.reactions.default);
 }
 
 function chooseEmoji(ev: MouseEvent) {
 	os.pickEmoji(ev.currentTarget ?? ev.target, {
 		showPinned: false,
 	}).then(emoji => {
-		if (!reactions.includes(emoji)) {
-			reactions.push(emoji);
+		if (!reactions.value.includes(emoji)) {
+			reactions.value.push(emoji);
 		}
 	});
 }
 
-watch($$(reactions), () => {
+watch(reactions, () => {
 	save();
 }, {
 	deep: true,
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.reaction,
diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue
index 71238af72e..0f6c30dae9 100644
--- a/packages/frontend/src/pages/settings/roles.vue
+++ b/packages/frontend/src/pages/settings/roles.vue
@@ -46,9 +46,9 @@ function save() {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.roles,
diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue
index eacd34778d..3f85f27e47 100644
--- a/packages/frontend/src/pages/settings/security.vue
+++ b/packages/frontend/src/pages/settings/security.vue
@@ -40,6 +40,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { computed } from 'vue';
 import X2fa from './2fa.vue';
 import FormSection from '@/components/form/section.vue';
 import FormSlot from '@/components/form/slot.vue';
@@ -97,9 +98,9 @@ async function regenerateToken() {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.security,
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index e549901f05..9fbcce2286 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -90,9 +90,9 @@ function reset() {
 	}
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.sounds,
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index 7103c2582a..b341e8488e 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref, computed } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XStatusbar from './statusbar.statusbar.vue';
 import MkFolder from '@/components/MkFolder.vue';
@@ -27,11 +27,11 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const statusbars = defaultStore.reactiveState.statusbars;
 
-let userLists = $ref();
+const userLists = ref();
 
 onMounted(() => {
 	os.api('users/lists/list').then(res => {
-		userLists = res;
+		userLists.value = res;
 	});
 });
 
@@ -45,9 +45,9 @@ async function add() {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.statusbar,
diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue
index 7fa7b23e44..c2ca53c743 100644
--- a/packages/frontend/src/pages/settings/theme.install.vue
+++ b/packages/frontend/src/pages/settings/theme.install.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkButton from '@/components/MkButton.vue';
 import { parseThemeCode, previewTheme, installTheme } from '@/scripts/install-theme.js';
@@ -25,7 +25,7 @@ import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let installThemeCode = $ref(null);
+const installThemeCode = ref(null);
 
 async function install(code: string): Promise<void> {
 	try {
@@ -55,9 +55,9 @@ async function install(code: string): Promise<void> {
 	}
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts._theme.install,
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index 8c90c175f0..2a2dd5e764 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -72,9 +72,9 @@ function uninstall() {
 	os.success();
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts._theme.manage,
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index 9e55a8fd8d..ad2fc6efe9 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -160,9 +160,9 @@ function setWallpaper(event) {
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.theme,
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index 3301732c88..c1695dc6a5 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkInput from '@/components/MkInput.vue';
 import FormSection from '@/components/form/section.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -62,36 +62,36 @@ const webhook = await os.api('i/webhooks/show', {
 	webhookId: props.webhookId,
 });
 
-let name = $ref(webhook.name);
-let url = $ref(webhook.url);
-let secret = $ref(webhook.secret);
-let active = $ref(webhook.active);
+const name = ref(webhook.name);
+const url = ref(webhook.url);
+const secret = ref(webhook.secret);
+const active = ref(webhook.active);
 
-let event_follow = $ref(webhook.on.includes('follow'));
-let event_followed = $ref(webhook.on.includes('followed'));
-let event_note = $ref(webhook.on.includes('note'));
-let event_reply = $ref(webhook.on.includes('reply'));
-let event_renote = $ref(webhook.on.includes('renote'));
-let event_reaction = $ref(webhook.on.includes('reaction'));
-let event_mention = $ref(webhook.on.includes('mention'));
+const event_follow = ref(webhook.on.includes('follow'));
+const event_followed = ref(webhook.on.includes('followed'));
+const event_note = ref(webhook.on.includes('note'));
+const event_reply = ref(webhook.on.includes('reply'));
+const event_renote = ref(webhook.on.includes('renote'));
+const event_reaction = ref(webhook.on.includes('reaction'));
+const event_mention = ref(webhook.on.includes('mention'));
 
 async function save(): Promise<void> {
 	const events = [];
-	if (event_follow) events.push('follow');
-	if (event_followed) events.push('followed');
-	if (event_note) events.push('note');
-	if (event_reply) events.push('reply');
-	if (event_renote) events.push('renote');
-	if (event_reaction) events.push('reaction');
-	if (event_mention) events.push('mention');
+	if (event_follow.value) events.push('follow');
+	if (event_followed.value) events.push('followed');
+	if (event_note.value) events.push('note');
+	if (event_reply.value) events.push('reply');
+	if (event_renote.value) events.push('renote');
+	if (event_reaction.value) events.push('reaction');
+	if (event_mention.value) events.push('mention');
 
 	os.apiWithDialog('i/webhooks/update', {
-		name,
-		url,
-		secret,
+		name: name.value,
+		url: url.value,
+		secret: secret.value,
 		webhookId: props.webhookId,
 		on: events,
-		active,
+		active: active.value,
 	});
 }
 
@@ -109,9 +109,9 @@ async function del(): Promise<void> {
 	router.push('/settings/webhook');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: 'Edit webhook',
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index ed56126548..8a4f03431c 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref, computed } from 'vue';
 import MkInput from '@/components/MkInput.vue';
 import FormSection from '@/components/form/section.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -48,39 +48,39 @@ import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let name = $ref('');
-let url = $ref('');
-let secret = $ref('');
+const name = ref('');
+const url = ref('');
+const secret = ref('');
 
-let event_follow = $ref(true);
-let event_followed = $ref(true);
-let event_note = $ref(true);
-let event_reply = $ref(true);
-let event_renote = $ref(true);
-let event_reaction = $ref(true);
-let event_mention = $ref(true);
+const event_follow = ref(true);
+const event_followed = ref(true);
+const event_note = ref(true);
+const event_reply = ref(true);
+const event_renote = ref(true);
+const event_reaction = ref(true);
+const event_mention = ref(true);
 
 async function create(): Promise<void> {
 	const events = [];
-	if (event_follow) events.push('follow');
-	if (event_followed) events.push('followed');
-	if (event_note) events.push('note');
-	if (event_reply) events.push('reply');
-	if (event_renote) events.push('renote');
-	if (event_reaction) events.push('reaction');
-	if (event_mention) events.push('mention');
+	if (event_follow.value) events.push('follow');
+	if (event_followed.value) events.push('followed');
+	if (event_note.value) events.push('note');
+	if (event_reply.value) events.push('reply');
+	if (event_renote.value) events.push('renote');
+	if (event_reaction.value) events.push('reaction');
+	if (event_mention.value) events.push('mention');
 
 	os.apiWithDialog('i/webhooks/create', {
-		name,
-		url,
-		secret,
+		name: name.value,
+		url: url.value,
+		secret: secret.value,
 		on: events,
 	});
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: 'Create new webhook',
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
index 33841d7f8d..334e5e841b 100644
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ b/packages/frontend/src/pages/settings/webhook.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
@@ -46,9 +46,9 @@ const pagination = {
 	noPaging: true,
 };
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: 'Webhook',
diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue
index 1d77e5931d..3e9cac9858 100644
--- a/packages/frontend/src/pages/share.vue
+++ b/packages/frontend/src/pages/share.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 // SPECIFICATION: https://misskey-hub.net/docs/features/share-form.html
 
-import { ref } from 'vue';
+import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
@@ -46,27 +46,27 @@ const localOnlyQuery = urlParams.get('localOnly');
 const visibilityQuery = urlParams.get('visibility') as typeof Misskey.noteVisibilities[number];
 
 const state = ref<'fetching' | 'writing' | 'posted'>('fetching');
-let title = $ref(urlParams.get('title'));
+const title = ref(urlParams.get('title'));
 const text = urlParams.get('text');
 const url = urlParams.get('url');
-let initialText = $ref<string | undefined>();
-let reply = $ref<Misskey.entities.Note | undefined>();
-let renote = $ref<Misskey.entities.Note | undefined>();
-let visibility = $ref(Misskey.noteVisibilities.includes(visibilityQuery) ? visibilityQuery : undefined);
-let localOnly = $ref(localOnlyQuery === '0' ? false : localOnlyQuery === '1' ? true : undefined);
-let files = $ref([] as Misskey.entities.DriveFile[]);
-let visibleUsers = $ref([] as Misskey.entities.User[]);
+const initialText = ref<string | undefined>();
+const reply = ref<Misskey.entities.Note | undefined>();
+const renote = ref<Misskey.entities.Note | undefined>();
+const visibility = ref(Misskey.noteVisibilities.includes(visibilityQuery) ? visibilityQuery : undefined);
+const localOnly = ref(localOnlyQuery === '0' ? false : localOnlyQuery === '1' ? true : undefined);
+const files = ref([] as Misskey.entities.DriveFile[]);
+const visibleUsers = ref([] as Misskey.entities.User[]);
 
 async function init() {
 	let noteText = '';
-	if (title) noteText += `[ ${title} ]\n`;
+	if (title.value) noteText += `[ ${title.value} ]\n`;
 	// Googleニュース対策
-	if (text?.startsWith(`${title}.\n`)) noteText += text.replace(`${title}.\n`, '');
-	else if (text && title !== text) noteText += `${text}\n`;
+	if (text?.startsWith(`${title.value}.\n`)) noteText += text.replace(`${title.value}.\n`, '');
+	else if (text && title.value !== text) noteText += `${text}\n`;
 	if (url) noteText += `${url}`;
-	initialText = noteText.trim();
+	initialText.value = noteText.trim();
 
-	if (visibility === 'specified') {
+	if (visibility.value === 'specified') {
 		const visibleUserIds = urlParams.get('visibleUserIds');
 		const visibleAccts = urlParams.get('visibleAccts');
 		await Promise.all(
@@ -78,7 +78,7 @@ async function init() {
 				.map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q)
 				.map(q => os.api('users/show', q)
 					.then(user => {
-						visibleUsers.push(user);
+						visibleUsers.value.push(user);
 					}, () => {
 						console.error(`Invalid user query: ${JSON.stringify(q)}`);
 					}),
@@ -91,7 +91,7 @@ async function init() {
 		const replyId = urlParams.get('replyId');
 		const replyUri = urlParams.get('replyUri');
 		if (replyId) {
-			reply = await os.api('notes/show', {
+			reply.value = await os.api('notes/show', {
 				noteId: replyId,
 			});
 		} else if (replyUri) {
@@ -99,7 +99,7 @@ async function init() {
 				uri: replyUri,
 			});
 			if (obj.type === 'Note') {
-				reply = obj.object;
+				reply.value = obj.object;
 			}
 		}
 		//#endregion
@@ -108,7 +108,7 @@ async function init() {
 		const renoteId = urlParams.get('renoteId');
 		const renoteUri = urlParams.get('renoteUri');
 		if (renoteId) {
-			renote = await os.api('notes/show', {
+			renote.value = await os.api('notes/show', {
 				noteId: renoteId,
 			});
 		} else if (renoteUri) {
@@ -116,7 +116,7 @@ async function init() {
 				uri: renoteUri,
 			});
 			if (obj.type === 'Note') {
-				renote = obj.object;
+				renote.value = obj.object;
 			}
 		}
 		//#endregion
@@ -128,7 +128,7 @@ async function init() {
 				fileIds.split(',')
 					.map(fileId => os.api('drive/files/show', { fileId })
 						.then(file => {
-							files.push(file);
+							files.value.push(file);
 						}, () => {
 							console.error(`Failed to fetch a file ${fileId}`);
 						}),
@@ -167,9 +167,9 @@ function onPosted(): void {
 	postMessageToParentWindow('misskey:shareForm:shareCompleted');
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.share,
diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue
index d9a730851d..638c7e8773 100644
--- a/packages/frontend/src/pages/signup-complete.vue
+++ b/packages/frontend/src/pages/signup-complete.vue
@@ -25,29 +25,29 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 
-let submitting = $ref(false);
+const submitting = ref(false);
 
 const props = defineProps<{
 	code: string;
 }>();
 
 function submit() {
-	if (submitting) return;
-	submitting = true;
+	if (submitting.value) return;
+	submitting.value = true;
 
 	os.api('signup-pending', {
 		code: props.code,
 	}).then(res => {
 		return login(res.i, '/');
 	}).catch(() => {
-		submitting = false;
+		submitting.value = false;
 
 		os.alert({
 			type: 'error',
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index 85c571ecd6..797ab796d2 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -51,9 +51,9 @@ async function post() {
 	notes.value?.pagingComponent?.reload();
 }
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: props.tag,
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 740fd5c4eb..0d137137fc 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, ref, computed } from 'vue';
 import { toUnicode } from 'punycode/';
 import tinycolor from 'tinycolor2';
 import { v4 as uuid } from 'uuid';
@@ -124,57 +124,57 @@ const fgColors = [
 	{ color: 'pink', forLight: '#84667d', forDark: '#e4d1e0', forPreview: '#b12390' },
 ];
 
-let theme = $ref<Partial<Theme>>({
+const theme = ref<Partial<Theme>>({
 	base: 'light',
 	props: lightTheme.props,
 });
-let description = $ref<string | null>(null);
-let themeCode = $ref<string | null>(null);
-let changed = $ref(false);
+const description = ref<string | null>(null);
+const themeCode = ref<string | null>(null);
+const changed = ref(false);
 
-useLeaveGuard($$(changed));
+useLeaveGuard(changed);
 
 function showPreview() {
 	os.pageWindow('/preview');
 }
 
 function setBgColor(color: typeof bgColors[number]) {
-	if (theme.base !== color.kind) {
+	if (theme.value.base !== color.kind) {
 		const base = color.kind === 'dark' ? darkTheme : lightTheme;
 		for (const prop of Object.keys(base.props)) {
 			if (prop === 'accent') continue;
 			if (prop === 'fg') continue;
-			theme.props[prop] = base.props[prop];
+			theme.value.props[prop] = base.props[prop];
 		}
 	}
-	theme.base = color.kind;
-	theme.props.bg = color.color;
+	theme.value.base = color.kind;
+	theme.value.props.bg = color.color;
 
-	if (theme.props.fg) {
-		const matchedFgColor = fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(theme.props.fg).toRgbString()));
+	if (theme.value.props.fg) {
+		const matchedFgColor = fgColors.find(x => [tinycolor(x.forLight).toRgbString(), tinycolor(x.forDark).toRgbString()].includes(tinycolor(theme.value.props.fg).toRgbString()));
 		if (matchedFgColor) setFgColor(matchedFgColor);
 	}
 }
 
 function setAccentColor(color) {
-	theme.props.accent = color;
+	theme.value.props.accent = color;
 }
 
 function setFgColor(color) {
-	theme.props.fg = theme.base === 'light' ? color.forLight : color.forDark;
+	theme.value.props.fg = theme.value.base === 'light' ? color.forLight : color.forDark;
 }
 
 function apply() {
-	themeCode = JSON5.stringify(theme, null, '\t');
-	applyTheme(theme, false);
-	changed = true;
+	themeCode.value = JSON5.stringify(theme.value, null, '\t');
+	applyTheme(theme.value, false);
+	changed.value = true;
 }
 
 function applyThemeCode() {
 	let parsed;
 
 	try {
-		parsed = JSON5.parse(themeCode);
+		parsed = JSON5.parse(themeCode.value);
 	} catch (err) {
 		os.alert({
 			type: 'error',
@@ -183,7 +183,7 @@ function applyThemeCode() {
 		return;
 	}
 
-	theme = parsed;
+	theme.value = parsed;
 }
 
 async function saveAs() {
@@ -193,27 +193,27 @@ async function saveAs() {
 	});
 	if (canceled) return;
 
-	theme.id = uuid();
-	theme.name = name;
-	theme.author = `@${$i.username}@${toUnicode(host)}`;
-	if (description) theme.desc = description;
-	await addTheme(theme);
-	applyTheme(theme);
+	theme.value.id = uuid();
+	theme.value.name = name;
+	theme.value.author = `@${$i.username}@${toUnicode(host)}`;
+	if (description.value) theme.value.desc = description.value;
+	await addTheme(theme.value);
+	applyTheme(theme.value);
 	if (defaultStore.state.darkMode) {
-		ColdDeviceStorage.set('darkTheme', theme);
+		ColdDeviceStorage.set('darkTheme', theme.value);
 	} else {
-		ColdDeviceStorage.set('lightTheme', theme);
+		ColdDeviceStorage.set('lightTheme', theme.value);
 	}
-	changed = false;
+	changed.value = false;
 	os.alert({
 		type: 'success',
-		text: i18n.t('_theme.installed', { name: theme.name }),
+		text: i18n.t('_theme.installed', { name: theme.value.name }),
 	});
 }
 
-watch($$(theme), apply, { deep: true });
+watch(theme, apply, { deep: true });
 
-const headerActions = $computed(() => [{
+const headerActions = computed(() => [{
 	asFullButton: true,
 	icon: 'ti ti-eye',
 	text: i18n.ts.preview,
@@ -225,7 +225,7 @@ const headerActions = $computed(() => [{
 	handler: saveAs,
 }]);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata({
 	title: i18n.ts.themeEditor,
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index b390d1931d..942061efe3 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, provide } from 'vue';
+import { computed, watch, provide, shallowRef, ref } from 'vue';
 import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
@@ -57,28 +57,28 @@ const keymap = {
 	't': focus,
 };
 
-const tlComponent = $shallowRef<InstanceType<typeof MkTimeline>>();
-const rootEl = $shallowRef<HTMLElement>();
+const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
+const rootEl = shallowRef<HTMLElement>();
 
-let queue = $ref(0);
-let srcWhenNotSignin = $ref(isLocalTimelineAvailable ? 'local' : 'global');
-const src = $computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin), set: (x) => saveSrc(x) });
-const withRenotes = $ref(true);
-const withReplies = $ref($i ? defaultStore.state.tlWithReplies : false);
-const onlyFiles = $ref(false);
+const queue = ref(0);
+const srcWhenNotSignin = ref(isLocalTimelineAvailable ? 'local' : 'global');
+const src = computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), set: (x) => saveSrc(x) });
+const withRenotes = ref(true);
+const withReplies = ref($i ? defaultStore.state.tlWithReplies : false);
+const onlyFiles = ref(false);
 
-watch($$(src), () => queue = 0);
+watch(src, () => queue.value = 0);
 
-watch($$(withReplies), (x) => {
+watch(withReplies, (x) => {
 	if ($i) defaultStore.set('tlWithReplies', x);
 });
 
 function queueUpdated(q: number): void {
-	queue = q;
+	queue.value = q;
 }
 
 function top(): void {
-	if (rootEl) scroll(rootEl, { top: 0 });
+	if (rootEl.value) scroll(rootEl.value, { top: 0 });
 }
 
 async function chooseList(ev: MouseEvent): Promise<void> {
@@ -125,7 +125,7 @@ function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | `list:${string
 		src: newSrc,
 		userList,
 	});
-	srcWhenNotSignin = newSrc;
+	srcWhenNotSignin.value = newSrc;
 }
 
 async function timetravel(): Promise<void> {
@@ -134,21 +134,21 @@ async function timetravel(): Promise<void> {
 	});
 	if (canceled) return;
 
-	tlComponent.timetravel(date);
+	tlComponent.value.timetravel(date);
 }
 
 function focus(): void {
-	tlComponent.focus();
+	tlComponent.value.focus();
 }
 
 function closeTutorial(): void {
-	if (!['home', 'local', 'social', 'global'].includes(src)) return;
+	if (!['home', 'local', 'social', 'global'].includes(src.value)) return;
 	const before = defaultStore.state.timelineTutorials;
-	before[src] = true;
+	before[src.value] = true;
 	defaultStore.set('timelineTutorials', before);
 }
 
-const headerActions = $computed(() => {
+const headerActions = computed(() => {
 	const tmp = [
 		{
 			icon: 'ti ti-dots',
@@ -157,17 +157,17 @@ const headerActions = $computed(() => {
 				os.popupMenu([{
 					type: 'switch',
 					text: i18n.ts.showRenotes,
-					ref: $$(withRenotes),
-				}, src === 'local' || src === 'social' ? {
+					ref: withRenotes,
+				}, src.value === 'local' || src.value === 'social' ? {
 					type: 'switch',
 					text: i18n.ts.showRepliesToOthersInTimeline,
-					ref: $$(withReplies),
-					disabled: $$(onlyFiles),
+					ref: withReplies,
+					disabled: onlyFiles,
 				} : undefined, {
 					type: 'switch',
 					text: i18n.ts.fileAttachedOnly,
-					ref: $$(onlyFiles),
-					disabled: src === 'local' || src === 'social' ? $$(withReplies) : false,
+					ref: onlyFiles,
+					disabled: src.value === 'local' || src.value === 'social' ? withReplies : false,
 				}], ev.currentTarget ?? ev.target);
 			},
 		},
@@ -178,14 +178,14 @@ const headerActions = $computed(() => {
 			text: i18n.ts.reload,
 			handler: (ev: Event) => {
 				console.log('called');
-				tlComponent.reloadTimeline();
+				tlComponent.value.reloadTimeline();
 			},
 		});
 	}
 	return tmp;
 });
 
-const headerTabs = $computed(() => [...(defaultStore.reactiveState.pinnedUserLists.value.map(l => ({
+const headerTabs = computed(() => [...(defaultStore.reactiveState.pinnedUserLists.value.map(l => ({
 	key: 'list:' + l.id,
 	title: l.name,
 	icon: 'ti ti-star',
@@ -227,7 +227,7 @@ const headerTabs = $computed(() => [...(defaultStore.reactiveState.pinnedUserLis
 	onClick: chooseChannel,
 }] as Tab[]);
 
-const headerTabsWhenNotLogin = $computed(() => [
+const headerTabsWhenNotLogin = computed(() => [
 	...(isLocalTimelineAvailable ? [{
 		key: 'local',
 		title: i18n.ts._timelines.local,
@@ -244,7 +244,7 @@ const headerTabsWhenNotLogin = $computed(() => [
 
 definePageMetadata(computed(() => ({
 	title: i18n.ts.timeline,
-	icon: src === 'local' ? 'ti ti-planet' : src === 'social' ? 'ti ti-universe' : src === 'global' ? 'ti ti-whirl' : 'ti ti-home',
+	icon: src.value === 'local' ? 'ti ti-planet' : src.value === 'social' ? 'ti ti-universe' : src.value === 'global' ? 'ti ti-whirl' : 'ti ti-home',
 })));
 </script>
 
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 0fc7b62d82..5804a7e7da 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref, shallowRef } from 'vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import { scroll } from '@/scripts/scroll.js';
 import * as os from '@/os.js';
@@ -38,39 +38,39 @@ const props = defineProps<{
 	listId: string;
 }>();
 
-let list = $ref(null);
-let queue = $ref(0);
-let tlEl = $shallowRef<InstanceType<typeof MkTimeline>>();
-let rootEl = $shallowRef<HTMLElement>();
+const list = ref(null);
+const queue = ref(0);
+const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
+const rootEl = shallowRef<HTMLElement>();
 
 watch(() => props.listId, async () => {
-	list = await os.api('users/lists/show', {
+	list.value = await os.api('users/lists/show', {
 		listId: props.listId,
 	});
 }, { immediate: true });
 
 function queueUpdated(q) {
-	queue = q;
+	queue.value = q;
 }
 
 function top() {
-	scroll(rootEl, { top: 0 });
+	scroll(rootEl.value, { top: 0 });
 }
 
 function settings() {
 	router.push(`/my/lists/${props.listId}`);
 }
 
-const headerActions = $computed(() => list ? [{
+const headerActions = computed(() => list.value ? [{
 	icon: 'ti ti-settings',
 	text: i18n.ts.settings,
 	handler: settings,
 }] : []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list ? {
-	title: list.name,
+definePageMetadata(computed(() => list.value ? {
+	title: list.value.name,
 	icon: 'ti ti-list',
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue
index 71f8d31924..06269ec9a9 100644
--- a/packages/frontend/src/pages/user-tag.vue
+++ b/packages/frontend/src/pages/user-tag.vue
@@ -25,7 +25,7 @@ const props = defineProps<{
 	tag: string;
 }>();
 
-const tagUsers = $computed(() => ({
+const tagUsers = computed(() => ({
 	endpoint: 'hashtags/users' as const,
 	limit: 30,
 	params: {
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index bc6bf77168..bd1159cb32 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import { Chart, ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
@@ -32,12 +32,12 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
-let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = shallowRef<HTMLCanvasElement>(null);
+const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
@@ -88,7 +88,7 @@ async function renderChart() {
 		}, extra);
 	}
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'bar',
 		data: {
 			datasets: [
@@ -162,10 +162,10 @@ async function renderChart() {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
 	});
 
-	fetching = false;
+	fetching.value = false;
 }
 
 onMounted(async () => {
diff --git a/packages/frontend/src/pages/user/activity.heatmap.vue b/packages/frontend/src/pages/user/activity.heatmap.vue
index 73bec2487e..ff46db9653 100644
--- a/packages/frontend/src/pages/user/activity.heatmap.vue
+++ b/packages/frontend/src/pages/user/activity.heatmap.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, nextTick, watch } from 'vue';
+import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
@@ -29,11 +29,11 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const rootEl = $shallowRef<HTMLDivElement>(null);
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
+const rootEl = shallowRef<HTMLDivElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip({
 	position: 'middle',
@@ -44,8 +44,8 @@ async function renderChart() {
 		chartInstance.destroy();
 	}
 
-	const wide = rootEl.offsetWidth > 700;
-	const narrow = rootEl.offsetWidth < 400;
+	const wide = rootEl.value.offsetWidth > 700;
+	const narrow = rootEl.value.offsetWidth < 400;
 
 	const weeks = wide ? 50 : narrow ? 10 : 25;
 	const chartLimit = 7 * weeks;
@@ -78,7 +78,7 @@ async function renderChart() {
 		values = raw.inc;
 	}
 
-	fetching = false;
+	fetching.value = false;
 
 	await nextTick();
 
@@ -91,7 +91,7 @@ async function renderChart() {
 
 	const marginEachCell = 4;
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'matrix',
 		data: {
 			datasets: [{
@@ -203,7 +203,7 @@ async function renderChart() {
 }
 
 watch(() => props.src, () => {
-	fetching = true;
+	fetching.value = true;
 	renderChart();
 });
 
diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index 5ba4af7ca1..dd035641d8 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import { Chart, ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
@@ -32,12 +32,12 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
-let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = shallowRef<HTMLCanvasElement>(null);
+const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 50;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
@@ -87,7 +87,7 @@ async function renderChart() {
 		}, extra);
 	}
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'bar',
 		data: {
 			datasets: [
@@ -161,10 +161,10 @@ async function renderChart() {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
 	});
 
-	fetching = false;
+	fetching.value = false;
 }
 
 onMounted(async () => {
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 54d1d0c1be..2dd9a1570f 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef, ref } from 'vue';
 import { Chart, ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
@@ -32,12 +32,12 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const chartEl = $shallowRef<HTMLCanvasElement>(null);
-let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = shallowRef<HTMLCanvasElement>(null);
+const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
-let fetching = $ref(true);
+const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
@@ -88,7 +88,7 @@ async function renderChart() {
 		}, extra);
 	}
 
-	chartInstance = new Chart(chartEl, {
+	chartInstance = new Chart(chartEl.value, {
 		type: 'bar',
 		data: {
 			datasets: [
@@ -172,10 +172,10 @@ async function renderChart() {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl.value)],
 	});
 
-	fetching = false;
+	fetching.value = false;
 }
 
 onMounted(async () => {
diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue
index b744f6aeec..7e893ae849 100644
--- a/packages/frontend/src/pages/user/followers.vue
+++ b/packages/frontend/src/pages/user/followers.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XFollowList from './follow-list.vue';
 import * as os from '@/os.js';
@@ -31,16 +31,16 @@ const props = withDefaults(defineProps<{
 }>(), {
 });
 
-let user = $ref<null | Misskey.entities.UserDetailed>(null);
-let error = $ref(null);
+const user = ref<null | Misskey.entities.UserDetailed>(null);
+const error = ref(null);
 
 function fetchUser(): void {
 	if (props.acct == null) return;
-	user = null;
+	user.value = null;
 	os.api('users/show', Misskey.acct.parse(props.acct)).then(u => {
-		user = u;
+		user.value = u;
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
@@ -48,15 +48,15 @@ watch(() => props.acct, fetchUser, {
 	immediate: true,
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => user ? {
+definePageMetadata(computed(() => user.value ? {
 	icon: 'ti ti-user',
-	title: user.name ? `${user.name} (@${user.username})` : `@${user.username}`,
+	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
 	subtitle: i18n.ts.followers,
-	userName: user,
-	avatar: user,
+	userName: user.value,
+	avatar: user.value,
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue
index 52f5207119..c5f51712f6 100644
--- a/packages/frontend/src/pages/user/following.vue
+++ b/packages/frontend/src/pages/user/following.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XFollowList from './follow-list.vue';
 import * as os from '@/os.js';
@@ -31,16 +31,16 @@ const props = withDefaults(defineProps<{
 }>(), {
 });
 
-let user = $ref<null | Misskey.entities.UserDetailed>(null);
-let error = $ref(null);
+const user = ref<null | Misskey.entities.UserDetailed>(null);
+const error = ref(null);
 
 function fetchUser(): void {
 	if (props.acct == null) return;
-	user = null;
+	user.value = null;
 	os.api('users/show', Misskey.acct.parse(props.acct)).then(u => {
-		user = u;
+		user.value = u;
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
@@ -48,15 +48,15 @@ watch(() => props.acct, fetchUser, {
 	immediate: true,
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => user ? {
+definePageMetadata(computed(() => user.value ? {
 	icon: 'ti ti-user',
-	title: user.name ? `${user.name} (@${user.username})` : `@${user.username}`,
+	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
 	subtitle: i18n.ts.following,
-	userName: user,
-	avatar: user,
+	userName: user.value,
+	avatar: user.value,
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 2e5dd705d0..e2b205a401 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -146,7 +146,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch } from 'vue';
+import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkNote from '@/components/MkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
@@ -198,19 +198,19 @@ const props = withDefaults(defineProps<{
 
 const router = useRouter();
 
-let user = $ref(props.user);
-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);
-let editModerationNote = $ref(false);
+const user = ref(props.user);
+const parallaxAnimationId = ref<null | number>(null);
+const narrow = ref<null | boolean>(null);
+const rootEl = ref<null | HTMLElement>(null);
+const bannerEl = ref<null | HTMLElement>(null);
+const memoTextareaEl = ref<null | HTMLElement>(null);
+const memoDraft = ref(props.user.memo);
+const isEditingMemo = ref(false);
+const moderationNote = ref(props.user.moderationNote);
+const editModerationNote = ref(false);
 
-watch($$(moderationNote), async () => {
-	await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote });
+watch(moderationNote, async () => {
+	await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
 });
 
 const pagination = {
@@ -221,32 +221,32 @@ const pagination = {
 	})),
 };
 
-const style = $computed(() => {
+const style = computed(() => {
 	if (props.user.bannerUrl == null) return {};
 	return {
 		backgroundImage: `url(${ props.user.bannerUrl })`,
 	};
 });
 
-const age = $computed(() => {
+const age = computed(() => {
 	return calcAge(props.user.birthday);
 });
 
 function menu(ev) {
-	const { menu, cleanup } = getUserMenu(user, router);
+	const { menu, cleanup } = getUserMenu(user.value, router);
 	os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
 }
 
 function parallaxLoop() {
-	parallaxAnimationId = window.requestAnimationFrame(parallaxLoop);
+	parallaxAnimationId.value = window.requestAnimationFrame(parallaxLoop);
 	parallax();
 }
 
 function parallax() {
-	const banner = bannerEl as any;
+	const banner = bannerEl.value as any;
 	if (banner == null) return;
 
-	const top = getScrollPosition(rootEl);
+	const top = getScrollPosition(rootEl.value);
 
 	if (top < 0) return;
 
@@ -256,33 +256,33 @@ function parallax() {
 }
 
 function showMemoTextarea() {
-	isEditingMemo = true;
+	isEditingMemo.value = true;
 	nextTick(() => {
-		memoTextareaEl?.focus();
+		memoTextareaEl.value?.focus();
 	});
 }
 
 function adjustMemoTextarea() {
-	if (!memoTextareaEl) return;
-	memoTextareaEl.style.height = '0px';
-	memoTextareaEl.style.height = `${memoTextareaEl.scrollHeight}px`;
+	if (!memoTextareaEl.value) return;
+	memoTextareaEl.value.style.height = '0px';
+	memoTextareaEl.value.style.height = `${memoTextareaEl.value.scrollHeight}px`;
 }
 
 async function updateMemo() {
 	await api('users/update-memo', {
-		memo: memoDraft,
+		memo: memoDraft.value,
 		userId: props.user.id,
 	});
-	isEditingMemo = false;
+	isEditingMemo.value = false;
 }
 
 watch([props.user], () => {
-	memoDraft = props.user.memo;
+	memoDraft.value = props.user.memo;
 });
 
 onMounted(() => {
 	window.requestAnimationFrame(parallaxLoop);
-	narrow = rootEl!.clientWidth < 1000;
+	narrow.value = rootEl.value!.clientWidth < 1000;
 
 	if (props.user.birthday) {
 		const m = new Date().getMonth() + 1;
@@ -301,8 +301,8 @@ onMounted(() => {
 });
 
 onUnmounted(() => {
-	if (parallaxAnimationId) {
-		window.cancelAnimationFrame(parallaxAnimationId);
+	if (parallaxAnimationId.value) {
+		window.cancelAnimationFrame(parallaxAnimationId.value);
 	}
 });
 </script>
diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue
index c531bda598..fe17de9061 100644
--- a/packages/frontend/src/pages/user/index.activity.vue
+++ b/packages/frontend/src/pages/user/index.activity.vue
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkChart from '@/components/MkChart.vue';
@@ -34,20 +34,20 @@ const props = withDefaults(defineProps<{
 	limit: 50,
 });
 
-let chartSrc = $ref('per-user-notes');
+const chartSrc = ref('per-user-notes');
 
 function showMenu(ev: MouseEvent) {
 	os.popupMenu([{
 		text: i18n.ts.notes,
-		active: chartSrc === 'per-user-notes',
+		active: chartSrc.value === 'per-user-notes',
 		action: () => {
-			chartSrc = 'per-user-notes';
+			chartSrc.value = 'per-user-notes';
 		},
 	}, {
 		text: i18n.ts.numberOfProfileView,
-		active: chartSrc === 'per-user-pv',
+		active: chartSrc.value === 'per-user-pv',
 		action: () => {
-			chartSrc = 'per-user-pv';
+			chartSrc.value = 'per-user-pv';
 		},
 	}, /*, {
 		text: i18n.ts.following,
diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.files.vue
index 72f95ec1a5..32561e6b0b 100644
--- a/packages/frontend/src/pages/user/index.files.vue
+++ b/packages/frontend/src/pages/user/index.files.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
 import { notePage } from '@/filters/note.js';
@@ -47,12 +47,12 @@ const props = defineProps<{
 	user: Misskey.entities.UserDetailed;
 }>();
 
-let fetching = $ref(true);
-let files = $ref<{
+const fetching = ref(true);
+const files = ref<{
 	note: Misskey.entities.Note;
 	file: Misskey.entities.DriveFile;
 }[]>([]);
-let showingFiles = $ref<string[]>([]);
+const showingFiles = ref<string[]>([]);
 
 function thumbnail(image: Misskey.entities.DriveFile): string {
 	return defaultStore.state.disableShowingAnimatedImages
@@ -68,13 +68,13 @@ onMounted(() => {
 	}).then(notes => {
 		for (const note of notes) {
 			for (const file of note.files) {
-				files.push({
+				files.value.push({
 					note,
 					file,
 				});
 			}
 		}
-		fetching = false;
+		fetching.value = false;
 	});
 });
 </script>
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index 50cc9a3311..4725aedeee 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, computed, watch } from 'vue';
+import { defineAsyncComponent, computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { acct as getAcct } from '@/filters/user.js';
 import * as os from '@/os.js';
@@ -54,17 +54,17 @@ const props = withDefaults(defineProps<{
 	page: 'home',
 });
 
-let tab = $ref(props.page);
-let user = $ref<null | Misskey.entities.UserDetailed>(null);
-let error = $ref(null);
+const tab = ref(props.page);
+const user = ref<null | Misskey.entities.UserDetailed>(null);
+const error = ref(null);
 
 function fetchUser(): void {
 	if (props.acct == null) return;
-	user = null;
+	user.value = null;
 	os.api('users/show', Misskey.acct.parse(props.acct)).then(u => {
-		user = u;
+		user.value = u;
 	}).catch(err => {
-		error = err;
+		error.value = err;
 	});
 }
 
@@ -72,9 +72,9 @@ watch(() => props.acct, fetchUser, {
 	immediate: true,
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => user ? [{
+const headerTabs = computed(() => user.value ? [{
 	key: 'home',
 	title: i18n.ts.overview,
 	icon: 'ti ti-home',
@@ -86,11 +86,11 @@ const headerTabs = $computed(() => user ? [{
 	key: 'activity',
 	title: i18n.ts.activity,
 	icon: 'ti ti-chart-line',
-}, ...(user.host == null ? [{
+}, ...(user.value.host == null ? [{
 	key: 'achievements',
 	title: i18n.ts.achievements,
 	icon: 'ti ti-medal',
-}] : []), ...($i && ($i.id === user.id)) || user.publicReactions ? [{
+}] : []), ...($i && ($i.id === user.value.id)) || user.value.publicReactions ? [{
 	key: 'reactions',
 	title: i18n.ts.reaction,
 	icon: 'ti ti-mood-happy',
@@ -120,15 +120,15 @@ const headerTabs = $computed(() => user ? [{
 	icon: 'ti ti-code',
 }] : []);
 
-definePageMetadata(computed(() => user ? {
+definePageMetadata(computed(() => user.value ? {
 	icon: 'ti ti-user',
-	title: user.name ? `${user.name} (@${user.username})` : `@${user.username}`,
-	subtitle: `@${getAcct(user)}`,
-	userName: user,
-	avatar: user,
-	path: `/@${user.username}`,
+	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+	subtitle: `@${getAcct(user.value)}`,
+	userName: user.value,
+	avatar: user.value,
+	path: `/@${user.value.username}`,
 	share: {
-		title: user.name,
+		title: user.value.name,
 	},
 } : null));
 </script>
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index 89d0eb9a83..a8ce4537e5 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XTimeline from './welcome.timeline.vue';
 import MarqueeText from '@/components/MkMarquee.vue';
@@ -48,8 +48,8 @@ import MkNumber from '@/components/MkNumber.vue';
 import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
 
-let meta = $ref<Misskey.entities.MetaResponse>();
-let instances = $ref<Misskey.entities.FederationInstance[]>();
+const meta = ref<Misskey.entities.MetaResponse>();
+const instances = ref<Misskey.entities.FederationInstance[]>();
 
 function getInstanceIcon(instance: Misskey.entities.FederationInstance): string {
 	if (!instance.iconUrl) {
@@ -60,14 +60,14 @@ function getInstanceIcon(instance: Misskey.entities.FederationInstance): string
 }
 
 os.api('meta', { detail: true }).then(_meta => {
-	meta = _meta;
+	meta.value = _meta;
 });
 
 os.apiGet('federation/instances', {
 	sort: '+pubSub',
 	limit: 20,
 }).then(_instances => {
-	instances = _instances;
+	instances.value = _instances;
 });
 </script>
 
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 0e6ce61781..61b86f993d 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import { host, version } from '@/config.js';
@@ -44,21 +44,21 @@ import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 
-let username = $ref('');
-let password = $ref('');
-let submitting = $ref(false);
+const username = ref('');
+const password = ref('');
+const submitting = ref(false);
 
 function submit() {
-	if (submitting) return;
-	submitting = true;
+	if (submitting.value) return;
+	submitting.value = true;
 
 	os.api('admin/accounts/create', {
-		username: username,
-		password: password,
+		username: username.value,
+		password: password.value,
 	}).then(res => {
 		return login(res.token);
 	}).catch(() => {
-		submitting = false;
+		submitting.value = false;
 
 		os.alert({
 			type: 'error',
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 8e2192074d..129131ce4a 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { onUpdated } from 'vue';
+import { onUpdated, ref, shallowRef } from 'vue';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
@@ -36,20 +36,20 @@ import * as os from '@/os.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
 import { $i } from '@/account.js';
 
-let notes = $ref<Misskey.entities.Note[]>([]);
-let isScrolling = $ref(false);
-let scrollEl = $shallowRef<HTMLElement>();
+const notes = ref<Misskey.entities.Note[]>([]);
+const isScrolling = ref(false);
+const scrollEl = shallowRef<HTMLElement>();
 
 os.apiGet('notes/featured').then(_notes => {
-	notes = _notes;
+	notes.value = _notes;
 });
 
 onUpdated(() => {
-	if (!scrollEl) return;
-	const container = getScrollContainer(scrollEl);
+	if (!scrollEl.value) return;
+	const container = getScrollContainer(scrollEl.value);
 	const containerHeight = container ? container.clientHeight : window.innerHeight;
-	if (scrollEl.offsetHeight > containerHeight) {
-		isScrolling = true;
+	if (scrollEl.value.offsetHeight > containerHeight) {
+		isScrolling.value = true;
 	}
 });
 </script>
diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue
index 2e6bb8b38f..f7d262cc8a 100644
--- a/packages/frontend/src/pages/welcome.vue
+++ b/packages/frontend/src/pages/welcome.vue
@@ -11,22 +11,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import XSetup from './welcome.setup.vue';
 import XEntrance from './welcome.entrance.a.vue';
 import { instanceName } from '@/config.js';
 import * as os from '@/os.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-let meta = $ref(null);
+const meta = ref(null);
 
 os.api('meta', { detail: true }).then(res => {
-	meta = res;
+	meta.value = res;
 });
 
-const headerActions = $computed(() => []);
+const headerActions = computed(() => []);
 
-const headerTabs = $computed(() => []);
+const headerTabs = computed(() => []);
 
 definePageMetadata(computed(() => ({
 	title: instanceName,
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index 7f8556d8d2..6b69e1accf 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -63,7 +63,7 @@ const XUpload = defineAsyncComponent(() => import('./upload.vue'));
 
 const dev = _DEV_;
 
-let notifications = $ref<Misskey.entities.Notification[]>([]);
+const notifications = ref<Misskey.entities.Notification[]>([]);
 
 function onNotification(notification: Misskey.entities.Notification, isClient = false) {
 	if (document.visibilityState === 'visible') {
@@ -72,13 +72,13 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
 			useStream().send('readNotification');
 		}
 
-		notifications.unshift(notification);
+		notifications.value.unshift(notification);
 		window.setTimeout(() => {
-			if (notifications.length > 3) notifications.pop();
+			if (notifications.value.length > 3) notifications.value.pop();
 		}, 500);
 
 		window.setTimeout(() => {
-			notifications = notifications.filter(x => x.id !== notification.id);
+			notifications.value = notifications.value.filter(x => x.id !== notification.id);
 		}, 6000);
 	}
 
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index a4ea916d23..c92695afed 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -49,7 +49,7 @@ const props = defineProps<{
 
 const instances = ref<Misskey.entities.FederationInstance[]>([]);
 const fetching = ref(true);
-let key = $ref(0);
+const key = ref(0);
 
 const tick = () => {
 	os.api('federation/instances', {
@@ -58,7 +58,7 @@ const tick = () => {
 	}).then(res => {
 		instances.value = res;
 		fetching.value = false;
-		key++;
+		key.value++;
 	});
 };
 
diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue
index c8e5b3a8af..58e109ad7f 100644
--- a/packages/frontend/src/ui/_common_/statusbar-rss.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue
@@ -44,7 +44,7 @@ const props = defineProps<{
 
 const items = ref([]);
 const fetching = ref(true);
-let key = $ref(0);
+const key = ref(0);
 
 const tick = () => {
 	window.fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => {
@@ -54,7 +54,7 @@ const tick = () => {
 			}
 			items.value = feed.items;
 			fetching.value = false;
-			key++;
+			key.value++;
 		});
 	});
 };
diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
index f1fcd315d0..6057174ba8 100644
--- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
@@ -50,7 +50,7 @@ const props = defineProps<{
 
 const notes = ref<Misskey.entities.Note[]>([]);
 const fetching = ref(true);
-let key = $ref(0);
+const key = ref(0);
 
 const tick = () => {
 	if (props.userListId == null) return;
@@ -59,7 +59,7 @@ const tick = () => {
 	}).then(res => {
 		notes.value = res;
 		fetching.value = false;
-		key++;
+		key.value++;
 	});
 };
 
diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue
index b09221f5d2..be6a4959ec 100644
--- a/packages/frontend/src/ui/_common_/stream-indicator.vue
+++ b/packages/frontend/src/ui/_common_/stream-indicator.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onUnmounted } from 'vue';
+import { onUnmounted, ref } from 'vue';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import MkButton from '@/components/MkButton.vue';
@@ -23,14 +23,14 @@ import { defaultStore } from '@/store.js';
 
 const zIndex = os.claimZIndex('high');
 
-let hasDisconnected = $ref(false);
+const hasDisconnected = ref(false);
 
 function onDisconnected() {
-	hasDisconnected = true;
+	hasDisconnected.value = true;
 }
 
 function resetDisconnected() {
-	hasDisconnected = false;
+	hasDisconnected.value = false;
 }
 
 function reload() {
diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue
index 3b78aa93cd..aa9f908cec 100644
--- a/packages/frontend/src/ui/classic.header.vue
+++ b/packages/frontend/src/ui/classic.header.vue
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted } from 'vue';
+import { computed, defineAsyncComponent, onMounted, ref } from 'vue';
 import { openInstanceMenu } from './_common_/common';
 import * as os from '@/os.js';
 import { navbarItemDef } from '@/navbar';
@@ -59,12 +59,12 @@ import { i18n } from '@/i18n.js';
 
 const WINDOW_THRESHOLD = 1400;
 
-let settingsWindowed = $ref(window.innerWidth > WINDOW_THRESHOLD);
-let menu = $ref(defaultStore.state.menu);
+const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD);
+const menu = ref(defaultStore.state.menu);
 // const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
-let otherNavItemIndicated = computed<boolean>(() => {
+const otherNavItemIndicated = computed<boolean>(() => {
 	for (const def in navbarItemDef) {
-		if (menu.includes(def)) continue;
+		if (menu.value.includes(def)) continue;
 		if (navbarItemDef[def].indicated) return true;
 	}
 	return false;
@@ -86,7 +86,7 @@ function openAccountMenu(ev: MouseEvent) {
 
 onMounted(() => {
 	window.addEventListener('resize', () => {
-		settingsWindowed = (window.innerWidth >= WINDOW_THRESHOLD);
+		settingsWindowed.value = (window.innerWidth >= WINDOW_THRESHOLD);
 	}, { passive: true });
 });
 
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index 01dbf1c9c0..402ab1efee 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, onMounted, computed, watch, nextTick } from 'vue';
+import { defineAsyncComponent, onMounted, computed, watch, nextTick, ref, shallowRef } from 'vue';
 import { openInstanceMenu } from './_common_/common.js';
 // import { host } from '@/config.js';
 import * as os from '@/os.js';
@@ -65,24 +65,24 @@ import { i18n } from '@/i18n.js';
 
 const WINDOW_THRESHOLD = 1400;
 
-const menu = $ref(defaultStore.state.menu);
+const menu = ref(defaultStore.state.menu);
 const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay'));
 const otherNavItemIndicated = computed<boolean>(() => {
 	for (const def in navbarItemDef) {
-		if (menu.includes(def)) continue;
+		if (menu.value.includes(def)) continue;
 		if (navbarItemDef[def].indicated) return true;
 	}
 	return false;
 });
-let el = $shallowRef<HTMLElement>();
+const el = shallowRef<HTMLElement>();
 // let accounts = $ref([]);
 // let connection = $ref(null);
-let iconOnly = $ref(false);
-let settingsWindowed = $ref(false);
+const iconOnly = ref(false);
+const settingsWindowed = ref(false);
 
 function calcViewState() {
-	iconOnly = (window.innerWidth <= WINDOW_THRESHOLD) || (menuDisplay.value === 'sideIcon');
-	settingsWindowed = (window.innerWidth > WINDOW_THRESHOLD);
+	iconOnly.value = (window.innerWidth <= WINDOW_THRESHOLD) || (menuDisplay.value === 'sideIcon');
+	settingsWindowed.value = (window.innerWidth > WINDOW_THRESHOLD);
 }
 
 function more(ev: MouseEvent) {
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 3323e813bb..1a9f939c83 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, ComputedRef, onMounted, provide } from 'vue';
+import { defineAsyncComponent, ComputedRef, onMounted, provide, ref, computed, shallowRef } from 'vue';
 import XSidebar from './classic.sidebar.vue';
 import XCommon from './_common_/common.vue';
 import { instanceName } from '@/config.js';
@@ -62,26 +62,26 @@ const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 
 const DESKTOP_THRESHOLD = 1100;
 
-let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
+const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
 
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-let widgetsShowing = $ref(false);
-let fullView = $ref(false);
-let globalHeaderHeight = $ref(0);
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const widgetsShowing = ref(false);
+const fullView = ref(false);
+const globalHeaderHeight = ref(0);
 const wallpaper = miLocalStorage.getItem('wallpaper') != null;
-const showMenuOnTop = $computed(() => defaultStore.state.menuDisplay === 'top');
-let live2d = $shallowRef<HTMLIFrameElement>();
-let widgetsLeft = $ref();
-let widgetsRight = $ref();
+const showMenuOnTop = computed(() => defaultStore.state.menuDisplay === 'top');
+const live2d = shallowRef<HTMLIFrameElement>();
+const widgetsLeft = ref();
+const widgetsRight = ref();
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	pageMetadata.value = info;
+	if (pageMetadata.value.value) {
+		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
 	}
 });
-provide('shouldHeaderThin', showMenuOnTop);
+provide('shouldHeaderThin', showMenuOnTop.value);
 provide('forceSpacerMin', true);
 
 function attachSticky(el) {
@@ -110,10 +110,10 @@ function onContextmenu(ev: MouseEvent) {
 		type: 'label',
 		text: path,
 	}, {
-		icon: fullView ? 'ti ti-minimize' : 'ti ti-maximize',
-		text: fullView ? i18n.ts.quitFullView : i18n.ts.fullView,
+		icon: fullView.value ? 'ti ti-minimize' : 'ti ti-maximize',
+		text: fullView.value ? i18n.ts.quitFullView : i18n.ts.fullView,
 		action: () => {
-			fullView = !fullView;
+			fullView.value = !fullView.value;
 		},
 	}, {
 		icon: 'ti ti-window-maximize',
@@ -154,13 +154,13 @@ defaultStore.loaded.then(() => {
 
 onMounted(() => {
 	window.addEventListener('resize', () => {
-		isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD);
+		isDesktop.value = (window.innerWidth >= DESKTOP_THRESHOLD);
 	}, { passive: true });
 
 	if (defaultStore.state.aiChanMode) {
-		const iframeRect = live2d.getBoundingClientRect();
+		const iframeRect = live2d.value.getBoundingClientRect();
 		window.addEventListener('mousemove', ev => {
-			live2d.contentWindow.postMessage({
+			live2d.value.contentWindow.postMessage({
 				type: 'moveCursor',
 				body: {
 					x: ev.clientX - iframeRect.left,
@@ -169,7 +169,7 @@ onMounted(() => {
 			}, '*');
 		}, { passive: true });
 		window.addEventListener('touchmove', ev => {
-			live2d.contentWindow.postMessage({
+			live2d.value.contentWindow.postMessage({
 				type: 'moveCursor',
 				body: {
 					x: ev.touches[0].clientX - iframeRect.left,
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 1d51e08f78..10a073243b 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -92,7 +92,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, ref, watch, shallowRef } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XCommon from './_common_/common.vue';
 import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store.js';
@@ -171,7 +171,7 @@ function showSettings() {
 	os.pageWindow('/settings/deck');
 }
 
-let columnsEl = $shallowRef<HTMLElement>();
+const columnsEl = shallowRef<HTMLElement>();
 
 const addColumn = async (ev) => {
 	const columns = [
@@ -212,7 +212,7 @@ const onContextmenu = (ev) => {
 
 function onWheel(ev: WheelEvent) {
 	if (ev.deltaX === 0) {
-		columnsEl.scrollLeft += ev.deltaY;
+		columnsEl.value.scrollLeft += ev.deltaY;
 	}
 }
 
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 1f4600d949..fe4d2a809c 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef } from 'vue';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
@@ -26,7 +26,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
 
 onMounted(() => {
 	if (props.column.antennaId == null) {
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index d2d279e5d7..de5d94b4f7 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -19,6 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
@@ -32,8 +33,8 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
-let channel = $shallowRef<Misskey.entities.Channel>();
+const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const channel = shallowRef<Misskey.entities.Channel>();
 
 if (props.column.channelId == null) {
 	setChannel();
@@ -58,14 +59,14 @@ async function setChannel() {
 }
 
 async function post() {
-	if (!channel || channel.id !== props.column.channelId) {
-		channel = await os.api('channels/show', {
+	if (!channel.value || channel.value.id !== props.column.channelId) {
+		channel.value = await os.api('channels/show', {
 			channelId: props.column.channelId,
 		});
 	}
 
 	os.post({
-		channel,
+		channel: channel.value,
 	});
 }
 
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 1a6b833b45..39a0279dea 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onBeforeUnmount, onMounted, provide, watch } from 'vue';
+import { onBeforeUnmount, onMounted, provide, watch, shallowRef, ref, computed } from 'vue';
 import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -67,16 +67,16 @@ const emit = defineEmits<{
 	(ev: 'headerWheel', ctx: WheelEvent): void;
 }>();
 
-let body = $shallowRef<HTMLDivElement | null>();
+const body = shallowRef<HTMLDivElement | null>();
 
-let dragging = $ref(false);
-watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
+const dragging = ref(false);
+watch(dragging, v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
 
-let draghover = $ref(false);
-let dropready = $ref(false);
+const draghover = ref(false);
+const dropready = ref(false);
 
-const isMainColumn = $computed(() => props.column.type === 'main');
-const active = $computed(() => props.column.active !== false);
+const isMainColumn = computed(() => props.column.type === 'main');
+const active = computed(() => props.column.active !== false);
 
 onMounted(() => {
 	os.deckGlobalEvents.on('column.dragStart', onOtherDragStart);
@@ -89,11 +89,11 @@ onBeforeUnmount(() => {
 });
 
 function onOtherDragStart() {
-	dropready = true;
+	dropready.value = true;
 }
 
 function onOtherDragEnd() {
-	dropready = false;
+	dropready.value = false;
 }
 
 function toggleActive() {
@@ -208,8 +208,8 @@ function onContextmenu(ev: MouseEvent) {
 }
 
 function goTop() {
-	if (body) {
-		body.scrollTo({
+	if (body.value) {
+		body.value.scrollTo({
 			top: 0,
 			behavior: 'smooth',
 		});
@@ -223,17 +223,17 @@ function onDragstart(ev) {
 	// Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう
 	// SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately
 	window.setTimeout(() => {
-		dragging = true;
+		dragging.value = true;
 	}, 10);
 }
 
 function onDragend(ev) {
-	dragging = false;
+	dragging.value = false;
 }
 
 function onDragover(ev) {
 	// 自分自身がドラッグされている場合
-	if (dragging) {
+	if (dragging.value) {
 		// 自分自身にはドロップさせない
 		ev.dataTransfer.dropEffect = 'none';
 	} else {
@@ -241,16 +241,16 @@ function onDragover(ev) {
 
 		ev.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none';
 
-		if (isDeckColumn) draghover = true;
+		if (isDeckColumn) draghover.value = true;
 	}
 }
 
 function onDragleave() {
-	draghover = false;
+	draghover.value = false;
 }
 
 function onDrop(ev) {
-	draghover = false;
+	draghover.value = false;
 	os.deckGlobalEvents.emit('column.dragEnd');
 
 	const id = ev.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_);
diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue
index 40c33ebdfc..fd08623462 100644
--- a/packages/frontend/src/ui/deck/direct-column.vue
+++ b/packages/frontend/src/ui/deck/direct-column.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import XColumn from './column.vue';
 import { Column } from './deck-store.js';
 import MkNotes from '@/components/MkNotes.vue';
@@ -30,11 +30,11 @@ const pagination = {
 	},
 };
 
-const tlComponent: InstanceType<typeof MkNotes> = $ref();
+const tlComponent = ref<InstanceType<typeof MkNotes>>();
 
 function reloadTimeline() {
 	return new Promise<void>((res) => {
-		tlComponent.pagingComponent?.reload().then(() => {
+		tlComponent.value.pagingComponent?.reload().then(() => {
 			res();
 		});
 	});
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 40e4dcee7e..854c8d453b 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch } from 'vue';
+import { watch, shallowRef, ref } from 'vue';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store';
 import MkTimeline from '@/components/MkTimeline.vue';
@@ -26,14 +26,14 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
-const withRenotes = $ref(props.column.withRenotes ?? true);
+const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const withRenotes = ref(props.column.withRenotes ?? true);
 
 if (props.column.listId == null) {
 	setList();
 }
 
-watch($$(withRenotes), v => {
+watch(withRenotes, v => {
 	updateColumn(props.column.id, {
 		withRenotes: v,
 	});
@@ -72,7 +72,7 @@ const menu = [
 	{
 		type: 'switch',
 		text: i18n.ts.showRenotes,
-		ref: $$(withRenotes),
+		ref: withRenotes,
 	},
 ];
 </script>
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index d54368c932..0c52957ec4 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, provide, shallowRef } from 'vue';
+import { ComputedRef, provide, shallowRef, ref } from 'vue';
 import XColumn from './column.vue';
 import { deckStore, Column } from '@/ui/deck/deck-store.js';
 import * as os from '@/os.js';
@@ -35,11 +35,11 @@ defineProps<{
 }>();
 
 const contents = shallowRef<HTMLElement>();
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
+	pageMetadata.value = info;
 });
 
 /*
diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue
index fc67fa144d..b011ba3ca2 100644
--- a/packages/frontend/src/ui/deck/mentions-column.vue
+++ b/packages/frontend/src/ui/deck/mentions-column.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import XColumn from './column.vue';
 import { Column } from './deck-store.js';
 import MkNotes from '@/components/MkNotes.vue';
@@ -22,11 +22,11 @@ defineProps<{
 	isStacked: boolean;
 }>();
 
-const tlComponent: InstanceType<typeof MkNotes> = $ref();
+const tlComponent = ref<InstanceType<typeof MkNotes>>();
 
 function reloadTimeline() {
 	return new Promise<void>((res) => {
-		tlComponent.pagingComponent?.reload().then(() => {
+		tlComponent.value.pagingComponent?.reload().then(() => {
 			res();
 		});
 	});
diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue
index 770e8ea820..e6729b4d58 100644
--- a/packages/frontend/src/ui/deck/notifications-column.vue
+++ b/packages/frontend/src/ui/deck/notifications-column.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, shallowRef } from 'vue';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
 import XNotifications from '@/components/MkNotifications.vue';
@@ -24,7 +24,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let notificationsComponent = $shallowRef<InstanceType<typeof XNotifications>>();
+const notificationsComponent = shallowRef<InstanceType<typeof XNotifications>>();
 
 function func() {
 	os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSelectWindow.vue')), {
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index 86d8878820..d9bcf8d95e 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, shallowRef } from 'vue';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
@@ -26,7 +26,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
 
 onMounted(() => {
 	if (props.column.roleId == null) {
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 41582bbfe3..7ed0f56d02 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, watch } from 'vue';
+import { onMounted, watch, ref, shallowRef } from 'vue';
 import XColumn from './column.vue';
 import { removeColumn, updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
@@ -47,28 +47,28 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let disabled = $ref(false);
-let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
+const disabled = ref(false);
+const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
 
 const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
 const isGlobalTimelineAvailable = (($i == null && instance.policies.gtlAvailable) || ($i != null && $i.policies.gtlAvailable));
-const withRenotes = $ref(props.column.withRenotes ?? true);
-const withReplies = $ref(props.column.withReplies ?? false);
-const onlyFiles = $ref(props.column.onlyFiles ?? false);
+const withRenotes = ref(props.column.withRenotes ?? true);
+const withReplies = ref(props.column.withReplies ?? false);
+const onlyFiles = ref(props.column.onlyFiles ?? false);
 
-watch($$(withRenotes), v => {
+watch(withRenotes, v => {
 	updateColumn(props.column.id, {
 		withRenotes: v,
 	});
 });
 
-watch($$(withReplies), v => {
+watch(withReplies, v => {
 	updateColumn(props.column.id, {
 		withReplies: v,
 	});
 });
 
-watch($$(onlyFiles), v => {
+watch(onlyFiles, v => {
 	updateColumn(props.column.id, {
 		onlyFiles: v,
 	});
@@ -78,7 +78,7 @@ onMounted(() => {
 	if (props.column.tl == null) {
 		setType();
 	} else if ($i) {
-		disabled = (
+		disabled.value = (
 			(!((instance.policies.ltlAvailable) || ($i.policies.ltlAvailable)) && ['local', 'social'].includes(props.column.tl)) ||
 			(!((instance.policies.gtlAvailable) || ($i.policies.gtlAvailable)) && ['global'].includes(props.column.tl)));
 	}
@@ -115,17 +115,17 @@ const menu = [{
 }, {
 	type: 'switch',
 	text: i18n.ts.showRenotes,
-	ref: $$(withRenotes),
+	ref: withRenotes,
 }, props.column.tl === 'local' || props.column.tl === 'social' ? {
 	type: 'switch',
 	text: i18n.ts.showRepliesToOthersInTimeline,
-	ref: $$(withReplies),
-	disabled: $$(onlyFiles),
+	ref: withReplies,
+	disabled: onlyFiles,
 } : undefined, {
 	type: 'switch',
 	text: i18n.ts.fileAttachedOnly,
-	ref: $$(onlyFiles),
-	disabled: props.column.tl === 'local' || props.column.tl === 'social' ? $$(withReplies) : false,
+	ref: onlyFiles,
+	disabled: props.column.tl === 'local' || props.column.tl === 'social' ? withReplies : false,
 }];
 </script>
 
diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue
index 5bd6d73976..ef35d885f3 100644
--- a/packages/frontend/src/ui/deck/widgets-column.vue
+++ b/packages/frontend/src/ui/deck/widgets-column.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { ref } from 'vue';
 import XColumn from './column.vue';
 import { addColumnWidget, Column, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store.js';
 import XWidgets from '@/components/MkWidgets.vue';
@@ -26,7 +26,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-let edit = $ref(false);
+const edit = ref(false);
 
 function addWidget(widget) {
 	addColumnWidget(props.column.id, widget);
@@ -45,7 +45,7 @@ function updateWidgets(widgets) {
 }
 
 function func() {
-	edit = !edit;
+	edit.value = !edit.value;
 }
 
 const menu = [{
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index cc5433f81c..f32f2de3df 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -14,19 +14,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { provide, ComputedRef } from 'vue';
+import { provide, ComputedRef, ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import { mainRouter } from '@/router.js';
 import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
 import { instanceName } from '@/config.js';
 
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	pageMetadata.value = info;
+	if (pageMetadata.value.value) {
+		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
 	}
 });
 
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 5ed1e76828..4721507f7e 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -127,16 +127,16 @@ window.addEventListener('resize', () => {
 	isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
 });
 
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-const widgetsShowing = $ref(false);
-const navFooter = $shallowRef<HTMLElement>();
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const widgetsShowing = ref(false);
+const navFooter = shallowRef<HTMLElement>();
 const contents = shallowRef<InstanceType<typeof MkStickyContainer>>();
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	pageMetadata.value = info;
+	if (pageMetadata.value.value) {
+		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
 	}
 });
 
@@ -216,16 +216,16 @@ function top() {
 	});
 }
 
-let navFooterHeight = $ref(0);
-provide<Ref<number>>(CURRENT_STICKY_BOTTOM, $$(navFooterHeight));
+const navFooterHeight = ref(0);
+provide<Ref<number>>(CURRENT_STICKY_BOTTOM, navFooterHeight);
 
-watch($$(navFooter), () => {
-	if (navFooter) {
-		navFooterHeight = navFooter.offsetHeight;
-		document.body.style.setProperty('--stickyBottom', `${navFooterHeight}px`);
+watch(navFooter, () => {
+	if (navFooter.value) {
+		navFooterHeight.value = navFooter.value.offsetHeight;
+		document.body.style.setProperty('--stickyBottom', `${navFooterHeight.value}px`);
 		document.body.style.setProperty('--minBottomSpacing', 'var(--minBottomSpacingMobile)');
 	} else {
-		navFooterHeight = 0;
+		navFooterHeight.value = 0;
 		document.body.style.setProperty('--stickyBottom', '0px');
 		document.body.style.setProperty('--minBottomSpacing', '0px');
 	}
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index 44988e6df3..376563bd56 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -13,10 +13,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
-let editMode = $ref(false);
+import { computed, ref } from 'vue';
+const editMode = ref(false);
 </script>
 <script lang="ts" setup>
-import { } from 'vue';
 import XWidgets from '@/components/MkWidgets.vue';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
@@ -30,7 +30,7 @@ const props = withDefaults(defineProps<{
 	place: null,
 });
 
-const widgets = $computed(() => {
+const widgets = computed(() => {
 	if (props.place === null) return defaultStore.reactiveState.widgets.value;
 	if (props.place === 'left') return defaultStore.reactiveState.widgets.value.filter(w => w.place === 'left');
 	return defaultStore.reactiveState.widgets.value.filter(w => w.place !== 'left');
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index b7ad184da1..8bf3a28d55 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -69,7 +69,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, onMounted, provide } from 'vue';
+import { ComputedRef, onMounted, provide, ref, computed } from 'vue';
 import XCommon from './_common_/common.vue';
 import { host, instanceName } from '@/config.js';
 import * as os from '@/os.js';
@@ -84,13 +84,13 @@ import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 
 const DESKTOP_THRESHOLD = 1100;
 
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	pageMetadata.value = info;
+	if (pageMetadata.value.value) {
+		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
 	}
 });
 
@@ -99,14 +99,14 @@ const announcements = {
 	limit: 10,
 };
 
-const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
+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 showMenu = ref(false);
+const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
+const narrow = ref(window.innerWidth < 1280);
+const meta = ref();
 
-const keymap = $computed(() => {
+const keymap = computed(() => {
 	return {
 		'd': () => {
 			if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
@@ -118,10 +118,10 @@ const keymap = $computed(() => {
 	};
 });
 
-const root = $computed(() => mainRouter.currentRoute.value.name === 'index');
+const root = computed(() => mainRouter.currentRoute.value.name === 'index');
 
 os.api('meta', { detail: true }).then(res => {
-	meta = res;
+	meta.value = res;
 });
 
 function signin() {
@@ -137,15 +137,15 @@ function signup() {
 }
 
 onMounted(() => {
-	if (!isDesktop) {
+	if (!isDesktop.value) {
 		window.addEventListener('resize', () => {
-			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true;
+			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
 		}, { passive: true });
 	}
 });
 
 defineExpose({
-	showMenu: $$(showMenu),
+	showMenu: showMenu,
 });
 </script>
 
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 4f0945eb48..b819b6ca0a 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -22,22 +22,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { provide, ComputedRef } from 'vue';
+import { provide, ComputedRef, ref } from 'vue';
 import XCommon from './_common_/common.vue';
 import { mainRouter } from '@/router.js';
 import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
 import { instanceName, ui } from '@/config.js';
 import { i18n } from '@/i18n.js';
 
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
 
 const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck';
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	pageMetadata.value = info;
+	if (pageMetadata.value.value) {
+		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
 	}
 });
 
diff --git a/packages/frontend/src/widgets/WidgetActivity.chart.vue b/packages/frontend/src/widgets/WidgetActivity.chart.vue
index 9cfd845ace..a207071324 100644
--- a/packages/frontend/src/widgets/WidgetActivity.chart.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.chart.vue
@@ -34,18 +34,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 const props = defineProps<{
 	activity: any[]
 }>();
 
-let viewBoxX: number = $ref(147);
-let viewBoxY: number = $ref(60);
-let zoom: number = $ref(1);
-let pos: number = $ref(0);
-let pointsNote: any = $ref(null);
-let pointsReply: any = $ref(null);
-let pointsRenote: any = $ref(null);
-let pointsTotal: any = $ref(null);
+const viewBoxX = ref(147);
+const viewBoxY = ref(60);
+const zoom = ref(1);
+const pos = ref(0);
+const pointsNote = ref<any>(null);
+const pointsReply = ref<any>(null);
+const pointsRenote = ref<any>(null);
+const pointsTotal = ref<any>(null);
 
 function dragListen(fn) {
 	window.addEventListener('mousemove', fn);
@@ -62,17 +63,17 @@ function dragClear(fn) {
 function onMousedown(ev) {
 	const clickX = ev.clientX;
 	const clickY = ev.clientY;
-	const baseZoom = zoom;
-	const basePos = pos;
+	const baseZoom = zoom.value;
+	const basePos = pos.value;
 
 	// 動かした時
 	dragListen(me => {
 		let moveLeft = me.clientX - clickX;
 		let moveTop = me.clientY - clickY;
 
-		zoom = Math.max(1, baseZoom + (-moveTop / 20));
-		pos = Math.min(0, basePos + moveLeft);
-		if (pos < -(((props.activity.length - 1) * zoom) - viewBoxX)) pos = -(((props.activity.length - 1) * zoom) - viewBoxX);
+		zoom.value = Math.max(1, baseZoom + (-moveTop / 20));
+		pos.value = Math.min(0, basePos + moveLeft);
+		if (pos.value < -(((props.activity.length - 1) * zoom.value) - viewBoxX.value)) pos.value = -(((props.activity.length - 1) * zoom.value) - viewBoxX.value);
 
 		render();
 	});
@@ -82,10 +83,10 @@ function render() {
 	const peak = Math.max(...props.activity.map(d => d.total));
 	if (peak !== 0) {
 		const activity = props.activity.slice().reverse();
-		pointsNote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.notes / peak)) * viewBoxY}`).join(' ');
-		pointsReply = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.replies / peak)) * viewBoxY}`).join(' ');
-		pointsRenote = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.renotes / peak)) * viewBoxY}`).join(' ');
-		pointsTotal = activity.map((d, i) => `${(i * zoom) + pos},${(1 - (d.total / peak)) * viewBoxY}`).join(' ');
+		pointsNote.value = activity.map((d, i) => `${(i * zoom.value) + pos.value},${(1 - (d.notes / peak)) * viewBoxY.value}`).join(' ');
+		pointsReply.value = activity.map((d, i) => `${(i * zoom.value) + pos.value},${(1 - (d.replies / peak)) * viewBoxY.value}`).join(' ');
+		pointsRenote.value = activity.map((d, i) => `${(i * zoom.value) + pos.value},${(1 - (d.renotes / peak)) * viewBoxY.value}`).join(' ');
+		pointsTotal.value = activity.map((d, i) => `${(i * zoom.value) + pos.value},${(1 - (d.total / peak)) * viewBoxY.value}`).join(' ');
 	}
 }
 </script>
diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
index 53b6020ffc..08037222d0 100644
--- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue
+++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
@@ -52,7 +52,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 const parser = new Parser();
 
 const root = ref<AsUiRoot>();
-const components: Ref<AsUiComponent>[] = $ref([]);
+const components = ref<Ref<AsUiComponent>[]>([]);
 
 async function run() {
 	const aiscript = new Interpreter({
@@ -60,7 +60,7 @@ async function run() {
 			storageKey: 'widget',
 			token: $i?.token,
 		}),
-		...registerAsUiLib(components, (_root) => {
+		...registerAsUiLib(components.value, (_root) => {
 			root.value = _root.value;
 		}),
 	}, {
diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue
index e4ea2c97dd..ca115cfcf7 100644
--- a/packages/frontend/src/widgets/WidgetClock.vue
+++ b/packages/frontend/src/widgets/WidgetClock.vue
@@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
@@ -134,15 +134,15 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const tzAbbrev = $computed(() => (widgetProps.timezone === null
+const tzAbbrev = computed(() => (widgetProps.timezone === null
 	? timezones.find((tz) => tz.name.toLowerCase() === Intl.DateTimeFormat().resolvedOptions().timeZone.toLowerCase())?.abbrev
 	: timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.abbrev) ?? '?');
 
-const tzOffset = $computed(() => widgetProps.timezone === null
+const tzOffset = computed(() => widgetProps.timezone === null
 	? 0 - new Date().getTimezoneOffset()
 	: timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.offset ?? 0);
 
-const tzOffsetLabel = $computed(() => (tzOffset >= 0 ? '+' : '-') + Math.floor(tzOffset / 60).toString().padStart(2, '0') + ':' + (tzOffset % 60).toString().padStart(2, '0'));
+const tzOffsetLabel = computed(() => (tzOffset.value >= 0 ? '+' : '-') + Math.floor(tzOffset.value / 60).toString().padStart(2, '0') + ':' + (tzOffset.value % 60).toString().padStart(2, '0'));
 
 defineExpose<WidgetComponentExpose>({
 	name,
diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue
index 9ff5f8dcef..ba7b82aad5 100644
--- a/packages/frontend/src/widgets/WidgetDigitalClock.vue
+++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue
@@ -14,6 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { computed } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import { timezones } from '@/scripts/timezones.js';
@@ -63,15 +64,15 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const tzAbbrev = $computed(() => (widgetProps.timezone === null
+const tzAbbrev = computed(() => (widgetProps.timezone === null
 	? timezones.find((tz) => tz.name.toLowerCase() === Intl.DateTimeFormat().resolvedOptions().timeZone.toLowerCase())?.abbrev
 	: timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.abbrev) ?? '?');
 
-const tzOffset = $computed(() => widgetProps.timezone === null
+const tzOffset = computed(() => widgetProps.timezone === null
 	? 0 - new Date().getTimezoneOffset()
 	: timezones.find((tz) => tz.name.toLowerCase() === widgetProps.timezone)?.offset ?? 0);
 
-const tzOffsetLabel = $computed(() => (tzOffset >= 0 ? '+' : '-') + Math.floor(tzOffset / 60).toString().padStart(2, '0') + ':' + (tzOffset % 60).toString().padStart(2, '0'));
+const tzOffsetLabel = computed(() => (tzOffset.value >= 0 ? '+' : '-') + Math.floor(tzOffset.value / 60).toString().padStart(2, '0') + ':' + (tzOffset.value % 60).toString().padStart(2, '0'));
 
 defineExpose<WidgetComponentExpose>({
 	name,
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index 4ae77e86fc..16e1a42da2 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { shallowRef } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
@@ -47,8 +47,8 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-let cloud = $shallowRef<InstanceType<typeof MkTagCloud> | null>();
-let activeInstances = $shallowRef(null);
+const cloud = shallowRef<InstanceType<typeof MkTagCloud> | null>();
+const activeInstances = shallowRef(null);
 
 function onInstanceClick(i) {
 	os.pageWindow(`/instance-info/${i.host}`);
@@ -59,8 +59,8 @@ useInterval(() => {
 		sort: '+latestRequestReceivedAt',
 		limit: 25,
 	}).then(res => {
-		activeInstances = res;
-		if (cloud) cloud.update();
+		activeInstances.value = res;
+		if (cloud.value) cloud.value.update();
 	});
 }, 1000 * 60 * 3, {
 	immediate: true,
diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue
index 5531794569..cca368ec8f 100644
--- a/packages/frontend/src/widgets/WidgetJobQueue.vue
+++ b/packages/frontend/src/widgets/WidgetJobQueue.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onUnmounted, reactive } from 'vue';
+import { onUnmounted, reactive, ref } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import { useStream } from '@/stream.js';
@@ -100,8 +100,8 @@ const current = reactive({
 	},
 });
 const prev = reactive({} as typeof current);
-let jammedAudioBuffer: AudioBuffer | null = $ref(null);
-let jammedSoundNodePlaying: boolean = $ref(false);
+const jammedAudioBuffer = ref<AudioBuffer | null>(null);
+const jammedSoundNodePlaying = ref<boolean>(false);
 
 if (defaultStore.state.sound_masterVolume) {
 	sound.loadAudio({
@@ -109,7 +109,7 @@ if (defaultStore.state.sound_masterVolume) {
 		volume: 1,
 	}).then(buf => {
 		if (!buf) throw new Error('[WidgetJobQueue] Failed to initialize AudioBuffer');
-		jammedAudioBuffer = buf;
+		jammedAudioBuffer.value = buf;
 	});
 }
 
@@ -125,11 +125,11 @@ const onStats = (stats) => {
 		current[domain].waiting = stats[domain].waiting;
 		current[domain].delayed = stats[domain].delayed;
 
-		if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer && !jammedSoundNodePlaying) {
-			const soundNode = sound.createSourceNode(jammedAudioBuffer, 1);
+		if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) {
+			const soundNode = sound.createSourceNode(jammedAudioBuffer.value, 1);
 			if (soundNode) {
-				jammedSoundNodePlaying = true;
-				soundNode.onended = () => jammedSoundNodePlaying = false;
+				jammedSoundNodePlaying.value = true;
+				soundNode.onended = () => jammedSoundNodePlaying.value = false;
 				soundNode.start();
 			}
 		}
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index 5540a09c71..be662e0ed1 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -72,7 +72,7 @@ const fetchEndpoint = computed(() => {
 	url.searchParams.set('url', widgetProps.url);
 	return url;
 });
-let intervalClear = $ref<(() => void) | undefined>();
+const intervalClear = ref<(() => void) | undefined>();
 
 const tick = () => {
 	if (document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;
@@ -87,10 +87,10 @@ const tick = () => {
 
 watch(() => fetchEndpoint, tick);
 watch(() => widgetProps.refreshIntervalSec, () => {
-	if (intervalClear) {
-		intervalClear();
+	if (intervalClear.value) {
+		intervalClear.value();
 	}
-	intervalClear = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
+	intervalClear.value = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
 		immediate: true,
 		afterMounted: true,
 	});
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 2b2a5233be..07f922bfec 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -101,9 +101,9 @@ const fetchEndpoint = computed(() => {
 	url.searchParams.set('url', widgetProps.url);
 	return url;
 });
-let intervalClear = $ref<(() => void) | undefined>();
+const intervalClear = ref<(() => void) | undefined>();
 
-let key = $ref(0);
+const key = ref(0);
 
 const tick = () => {
 	if (document.visibilityState === 'hidden' && rawItems.value.length !== 0) return;
@@ -113,16 +113,16 @@ const tick = () => {
 		.then(feed => {
 			rawItems.value = feed.items ?? [];
 			fetching.value = false;
-			key++;
+			key.value++;
 		});
 };
 
 watch(() => fetchEndpoint, tick);
 watch(() => widgetProps.refreshIntervalSec, () => {
-	if (intervalClear) {
-		intervalClear();
+	if (intervalClear.value) {
+		intervalClear.value();
 	}
-	intervalClear = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
+	intervalClear.value = useInterval(tick, Math.max(10000, widgetProps.refreshIntervalSec * 1000), {
 		immediate: true,
 		afterMounted: true,
 	});
diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue
index a0d460c704..4f3ce1c8c5 100644
--- a/packages/frontend/src/widgets/WidgetUserList.vue
+++ b/packages/frontend/src/widgets/WidgetUserList.vue
@@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
@@ -57,9 +58,9 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	emit,
 );
 
-let list = $ref();
-let users = $ref([]);
-let fetching = $ref(true);
+const list = ref();
+const users = ref([]);
+const fetching = ref(true);
 
 async function chooseList() {
 	const lists = await os.api('users/lists/list');
@@ -79,19 +80,19 @@ async function chooseList() {
 
 const fetch = () => {
 	if (widgetProps.listId == null) {
-		fetching = false;
+		fetching.value = false;
 		return;
 	}
 
 	os.api('users/lists/show', {
 		listId: widgetProps.listId,
 	}).then(_list => {
-		list = _list;
+		list.value = _list;
 		os.api('users/show', {
-			userIds: list.userIds,
+			userIds: list.value.userIds,
 		}).then(_users => {
-			users = _users;
-			fetching = false;
+			users.value = _users;
+			fetching.value = false;
 		});
 	});
 };
diff --git a/packages/frontend/src/widgets/server-metric/cpu-mem.vue b/packages/frontend/src/widgets/server-metric/cpu-mem.vue
index c656d75429..9196ae209f 100644
--- a/packages/frontend/src/widgets/server-metric/cpu-mem.vue
+++ b/packages/frontend/src/widgets/server-metric/cpu-mem.vue
@@ -75,7 +75,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
+import { onMounted, onBeforeUnmount, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 
 const props = defineProps<{
@@ -83,23 +83,23 @@ const props = defineProps<{
 	meta: any
 }>();
 
-let viewBoxX: number = $ref(50);
-let viewBoxY: number = $ref(30);
-let stats: any[] = $ref([]);
+const viewBoxX = ref<number>(50);
+const viewBoxY = ref<number>(30);
+const stats = ref<any[]>([]);
 const cpuGradientId = uuid();
 const cpuMaskId = uuid();
 const memGradientId = uuid();
 const memMaskId = uuid();
-let cpuPolylinePoints: string = $ref('');
-let memPolylinePoints: string = $ref('');
-let cpuPolygonPoints: string = $ref('');
-let memPolygonPoints: string = $ref('');
-let cpuHeadX: any = $ref(null);
-let cpuHeadY: any = $ref(null);
-let memHeadX: any = $ref(null);
-let memHeadY: any = $ref(null);
-let cpuP: string = $ref('');
-let memP: string = $ref('');
+const cpuPolylinePoints = ref<string>('');
+const memPolylinePoints = ref<string>('');
+const cpuPolygonPoints = ref<string>('');
+const memPolygonPoints = ref<string>('');
+const cpuHeadX = ref<any>(null);
+const cpuHeadY = ref<any>(null);
+const memHeadX = ref<any>(null);
+const memHeadY = ref<any>(null);
+const cpuP = ref<string>('');
+const memP = ref<string>('');
 
 onMounted(() => {
 	props.connection.on('stats', onStats);
@@ -115,24 +115,24 @@ onBeforeUnmount(() => {
 });
 
 function onStats(connStats) {
-	stats.push(connStats);
-	if (stats.length > 50) stats.shift();
+	stats.value.push(connStats);
+	if (stats.value.length > 50) stats.value.shift();
 
-	let cpuPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - s.cpu) * viewBoxY]);
-	let memPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.mem.active / props.meta.mem.total)) * viewBoxY]);
-	cpuPolylinePoints = cpuPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-	memPolylinePoints = memPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	let cpuPolylinePointsStats = stats.value.map((s, i) => [viewBoxX.value - ((stats.value.length - 1) - i), (1 - s.cpu) * viewBoxY.value]);
+	let memPolylinePointsStats = stats.value.map((s, i) => [viewBoxX.value - ((stats.value.length - 1) - i), (1 - (s.mem.active / props.meta.mem.total)) * viewBoxY.value]);
+	cpuPolylinePoints.value = cpuPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	memPolylinePoints.value = memPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 
-	cpuPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${cpuPolylinePoints} ${viewBoxX},${viewBoxY}`;
-	memPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${memPolylinePoints} ${viewBoxX},${viewBoxY}`;
+	cpuPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${viewBoxY.value} ${cpuPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`;
+	memPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${viewBoxY.value} ${memPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`;
 
-	cpuHeadX = cpuPolylinePointsStats.at(-1)![0];
-	cpuHeadY = cpuPolylinePointsStats.at(-1)![1];
-	memHeadX = memPolylinePointsStats.at(-1)![0];
-	memHeadY = memPolylinePointsStats.at(-1)![1];
+	cpuHeadX.value = cpuPolylinePointsStats.at(-1)![0];
+	cpuHeadY.value = cpuPolylinePointsStats.at(-1)![1];
+	memHeadX.value = memPolylinePointsStats.at(-1)![0];
+	memHeadY.value = memPolylinePointsStats.at(-1)![1];
 
-	cpuP = (connStats.cpu * 100).toFixed(0);
-	memP = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0);
+	cpuP.value = (connStats.cpu * 100).toFixed(0);
+	memP.value = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0);
 }
 
 function onStatsLog(statsLog) {
diff --git a/packages/frontend/src/widgets/server-metric/cpu.vue b/packages/frontend/src/widgets/server-metric/cpu.vue
index 1c9cce8598..0aeba518c0 100644
--- a/packages/frontend/src/widgets/server-metric/cpu.vue
+++ b/packages/frontend/src/widgets/server-metric/cpu.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
+import { onMounted, onBeforeUnmount, ref } from 'vue';
 import XPie from './pie.vue';
 
 const props = defineProps<{
@@ -23,10 +23,10 @@ const props = defineProps<{
 	meta: any
 }>();
 
-let usage: number = $ref(0);
+const usage = ref<number>(0);
 
 function onStats(stats) {
-	usage = stats.cpu;
+	usage.value = stats.cpu;
 }
 
 onMounted(() => {
diff --git a/packages/frontend/src/widgets/server-metric/disk.vue b/packages/frontend/src/widgets/server-metric/disk.vue
index 079b326fd6..ef88cae9f6 100644
--- a/packages/frontend/src/widgets/server-metric/disk.vue
+++ b/packages/frontend/src/widgets/server-metric/disk.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 import XPie from './pie.vue';
 import bytes from '@/filters/bytes.js';
 
@@ -24,10 +24,10 @@ const props = defineProps<{
 	meta: any; // TODO
 }>();
 
-const usage = $computed(() => props.meta.fs.used / props.meta.fs.total);
-const total = $computed(() => props.meta.fs.total);
-const used = $computed(() => props.meta.fs.used);
-const available = $computed(() => props.meta.fs.total - props.meta.fs.used);
+const usage = computed(() => props.meta.fs.used / props.meta.fs.total);
+const total = computed(() => props.meta.fs.total);
+const used = computed(() => props.meta.fs.used);
+const available = computed(() => props.meta.fs.total - props.meta.fs.used);
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/widgets/server-metric/mem.vue b/packages/frontend/src/widgets/server-metric/mem.vue
index 47f5a70a7e..11d0c156c1 100644
--- a/packages/frontend/src/widgets/server-metric/mem.vue
+++ b/packages/frontend/src/widgets/server-metric/mem.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
+import { onMounted, onBeforeUnmount, ref } from 'vue';
 import XPie from './pie.vue';
 import bytes from '@/filters/bytes.js';
 
@@ -25,16 +25,16 @@ const props = defineProps<{
 	meta: any
 }>();
 
-let usage: number = $ref(0);
-let total: number = $ref(0);
-let used: number = $ref(0);
-let free: number = $ref(0);
+const usage = ref<number>(0);
+const total = ref<number>(0);
+const used = ref<number>(0);
+const free = ref<number>(0);
 
 function onStats(stats) {
-	usage = stats.mem.active / props.meta.mem.total;
-	total = props.meta.mem.total;
-	used = stats.mem.active;
-	free = total - used;
+	usage.value = stats.mem.active / props.meta.mem.total;
+	total.value = props.meta.mem.total;
+	used.value = stats.mem.active;
+	free.value = total.value - used.value;
 }
 
 onMounted(() => {
diff --git a/packages/frontend/src/widgets/server-metric/net.vue b/packages/frontend/src/widgets/server-metric/net.vue
index 5593128660..e6a8bfc22a 100644
--- a/packages/frontend/src/widgets/server-metric/net.vue
+++ b/packages/frontend/src/widgets/server-metric/net.vue
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onBeforeUnmount } from 'vue';
+import { onMounted, onBeforeUnmount, ref } from 'vue';
 import bytes from '@/filters/bytes.js';
 
 const props = defineProps<{
@@ -57,19 +57,19 @@ const props = defineProps<{
 	meta: any
 }>();
 
-let viewBoxX: number = $ref(50);
-let viewBoxY: number = $ref(30);
-let stats: any[] = $ref([]);
-let inPolylinePoints: string = $ref('');
-let outPolylinePoints: string = $ref('');
-let inPolygonPoints: string = $ref('');
-let outPolygonPoints: string = $ref('');
-let inHeadX: any = $ref(null);
-let inHeadY: any = $ref(null);
-let outHeadX: any = $ref(null);
-let outHeadY: any = $ref(null);
-let inRecent: number = $ref(0);
-let outRecent: number = $ref(0);
+const viewBoxX = ref<number>(50);
+const viewBoxY = ref<number>(30);
+const stats = ref<any[]>([]);
+const inPolylinePoints = ref<string>('');
+const outPolylinePoints = ref<string>('');
+const inPolygonPoints = ref<string>('');
+const outPolygonPoints = ref<string>('');
+const inHeadX = ref<any>(null);
+const inHeadY = ref<any>(null);
+const outHeadX = ref<any>(null);
+const outHeadY = ref<any>(null);
+const inRecent = ref<number>(0);
+const outRecent = ref<number>(0);
 
 onMounted(() => {
 	props.connection.on('stats', onStats);
@@ -85,27 +85,27 @@ onBeforeUnmount(() => {
 });
 
 function onStats(connStats) {
-	stats.push(connStats);
-	if (stats.length > 50) stats.shift();
+	stats.value.push(connStats);
+	if (stats.value.length > 50) stats.value.shift();
 
-	const inPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.rx)));
-	const outPeak = Math.max(1024 * 64, Math.max(...stats.map(s => s.net.tx)));
+	const inPeak = Math.max(1024 * 64, Math.max(...stats.value.map(s => s.net.rx)));
+	const outPeak = Math.max(1024 * 64, Math.max(...stats.value.map(s => s.net.tx)));
 
-	let inPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * viewBoxY]);
-	let outPolylinePointsStats = stats.map((s, i) => [viewBoxX - ((stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * viewBoxY]);
-	inPolylinePoints = inPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
-	outPolylinePoints = outPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	let inPolylinePointsStats = stats.value.map((s, i) => [viewBoxX.value - ((stats.value.length - 1) - i), (1 - (s.net.rx / inPeak)) * viewBoxY.value]);
+	let outPolylinePointsStats = stats.value.map((s, i) => [viewBoxX.value - ((stats.value.length - 1) - i), (1 - (s.net.tx / outPeak)) * viewBoxY.value]);
+	inPolylinePoints.value = inPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
+	outPolylinePoints.value = outPolylinePointsStats.map(xy => `${xy[0]},${xy[1]}`).join(' ');
 
-	inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
-	outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
+	inPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${viewBoxY.value} ${inPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`;
+	outPolygonPoints.value = `${viewBoxX.value - (stats.value.length - 1)},${viewBoxY.value} ${outPolylinePoints.value} ${viewBoxX.value},${viewBoxY.value}`;
 
-	inHeadX = inPolylinePointsStats.at(-1)![0];
-	inHeadY = inPolylinePointsStats.at(-1)![1];
-	outHeadX = outPolylinePointsStats.at(-1)![0];
-	outHeadY = outPolylinePointsStats.at(-1)![1];
+	inHeadX.value = inPolylinePointsStats.at(-1)![0];
+	inHeadY.value = inPolylinePointsStats.at(-1)![1];
+	outHeadX.value = outPolylinePointsStats.at(-1)![0];
+	outHeadY.value = outPolylinePointsStats.at(-1)![1];
 
-	inRecent = connStats.net.rx;
-	outRecent = connStats.net.tx;
+	inRecent.value = connStats.net.rx;
+	outRecent.value = connStats.net.tx;
 }
 
 function onStatsLog(statsLog) {
diff --git a/packages/frontend/src/widgets/server-metric/pie.vue b/packages/frontend/src/widgets/server-metric/pie.vue
index c8a1496101..fd18a6a4f2 100644
--- a/packages/frontend/src/widgets/server-metric/pie.vue
+++ b/packages/frontend/src/widgets/server-metric/pie.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed } from 'vue';
 
 const props = defineProps<{
 	value: number;
@@ -36,8 +36,8 @@ const props = defineProps<{
 
 const r = 0.45;
 
-const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`);
-const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2)));
+const color = computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`);
+const strokeDashoffset = computed(() => (1 - props.value) * (Math.PI * (r * 2)));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
index ad8fd773b0..5d451c878c 100644
--- a/packages/frontend/tsconfig.json
+++ b/packages/frontend/tsconfig.json
@@ -33,7 +33,6 @@
 		],
 		"types": [
 			"vite/client",
-			"reactivity-transform/macros-global"
 		],
 		"lib": [
 			"esnext",
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 4d2bb00e33..8ff3eb1562 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -2,8 +2,6 @@ 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';
@@ -50,10 +48,7 @@ export function getConfig(): UserConfig {
 		},
 
 		plugins: [
-			pluginVue({
-				reactivityTransform: true,
-			}),
-			ReactivityTransform(),
+			pluginVue(),
 			pluginUnwindCssModuleClassName(),
 			pluginJson5(),
 			...process.env.NODE_ENV === 'production'
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index ea1ea6cd18..707deb9f10 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -676,9 +676,6 @@ importers:
       '@vitejs/plugin-vue':
         specifier: 4.5.1
         version: 4.5.1(vite@5.0.5)(vue@3.3.9)
-      '@vue-macros/reactivity-transform':
-        specifier: 0.4.0
-        version: 0.4.0(rollup@4.6.1)(vue@3.3.9)
       '@vue/compiler-sfc':
         specifier: 3.3.9
         version: 3.3.9
@@ -1869,14 +1866,14 @@ packages:
     resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15:
     resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helper-compilation-targets@7.22.10:
@@ -1991,7 +1988,7 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/template': 7.22.5
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
     dev: true
 
   /@babel/helper-function-name@7.23.0:
@@ -2006,21 +2003,21 @@ packages:
     resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
     dev: true
 
   /@babel/helper-member-expression-to-functions@7.22.5:
     resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helper-member-expression-to-functions@7.23.0:
     resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helper-module-imports@7.22.15:
@@ -2069,7 +2066,7 @@ packages:
     resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helper-plugin-utils@7.22.5:
@@ -2124,7 +2121,7 @@ packages:
     resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helper-split-export-declaration@7.22.6:
@@ -2150,6 +2147,7 @@ packages:
   /@babel/helper-validator-identifier@7.22.20:
     resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
     engines: {node: '>=6.9.0'}
+    dev: true
 
   /@babel/helper-validator-identifier@7.22.5:
     resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==}
@@ -2172,7 +2170,7 @@ packages:
     dependencies:
       '@babel/helper-function-name': 7.22.5
       '@babel/template': 7.22.15
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
     dev: true
 
   /@babel/helpers@7.22.11:
@@ -3324,7 +3322,7 @@ packages:
     dependencies:
       '@babel/core': 7.23.5
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
       esutils: 2.0.3
     dev: true
 
@@ -3401,7 +3399,7 @@ packages:
       '@babel/helper-hoist-variables': 7.22.5
       '@babel/helper-split-export-declaration': 7.22.6
       '@babel/parser': 7.23.3
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
       debug: 4.3.4(supports-color@5.5.0)
       globals: 11.12.0
     transitivePeerDependencies:
@@ -3443,14 +3441,6 @@ packages:
       '@babel/helper-validator-identifier': 7.22.15
       to-fast-properties: 2.0.0
 
-  /@babel/types@7.23.3:
-    resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-string-parser': 7.22.5
-      '@babel/helper-validator-identifier': 7.22.20
-      to-fast-properties: 2.0.0
-
   /@babel/types@7.23.5:
     resolution: {integrity: sha512-ON5kSOJwVO6xXVRTvOI0eOnWe7VdUcIpsovGo9U/Br4Ie4UVFQTboO2cYnDhAGU6Fp+UxSiT+pMft0SMHfuq6w==}
     engines: {node: '>=6.9.0'}
@@ -6671,7 +6661,7 @@ packages:
     dependencies:
       '@babel/core': 7.23.5
       '@babel/preset-env': 7.23.5(@babel/core@7.23.5)
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
       '@ndelangen/get-tarball': 3.0.7
       '@storybook/codemod': 7.6.3
       '@storybook/core-common': 7.6.3
@@ -6734,7 +6724,7 @@ packages:
     dependencies:
       '@babel/core': 7.23.5
       '@babel/preset-env': 7.23.5(@babel/core@7.23.5)
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
       '@storybook/csf': 0.1.2
       '@storybook/csf-tools': 7.6.3
       '@storybook/node-logger': 7.6.3
@@ -6901,7 +6891,7 @@ packages:
     resolution: {integrity: sha512-8bMYPsWw2tv+fqZ5H436l4x1KLSB6gIcm6snsjyF916yCHG6WcWm+EI6+wNUoySEtrQY2AiwFJqE37wI5OUJFg==}
     dependencies:
       '@storybook/csf-tools': 7.6.3
-      unplugin: 1.5.1
+      unplugin: 1.4.0
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -6912,7 +6902,7 @@ packages:
       '@babel/generator': 7.23.5
       '@babel/parser': 7.23.3
       '@babel/traverse': 7.23.5
-      '@babel/types': 7.23.3
+      '@babel/types': 7.23.5
       '@storybook/csf': 0.1.2
       '@storybook/types': 7.6.3
       fs-extra: 11.1.1
@@ -7763,7 +7753,7 @@ packages:
     resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
     dependencies:
       '@babel/parser': 7.23.3
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
       '@types/babel__generator': 7.6.4
       '@types/babel__template': 7.4.1
       '@types/babel__traverse': 7.20.0
@@ -7772,20 +7762,20 @@ packages:
   /@types/babel__generator@7.6.4:
     resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
     dev: true
 
   /@types/babel__template@7.4.1:
     resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==}
     dependencies:
       '@babel/parser': 7.23.3
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
     dev: true
 
   /@types/babel__traverse@7.20.0:
     resolution: {integrity: sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==}
     dependencies:
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
     dev: true
 
   /@types/bcryptjs@2.4.6:
@@ -8726,52 +8716,6 @@ packages:
       path-browserify: 1.0.1
     dev: true
 
-  /@vue-macros/common@1.9.0(rollup@4.6.1)(vue@3.3.9):
-    resolution: {integrity: sha512-LbfRHDkceuokkLlVuQW9Wq3ZLmRs6KIDPzCjUvvL14HB4GslWdtvBB1suFfNs6VMvh9Zj30cEKF/EAP7QBCZ6Q==}
-    engines: {node: '>=16.14.0'}
-    peerDependencies:
-      vue: ^2.7.0 || ^3.2.25
-    peerDependenciesMeta:
-      vue:
-        optional: true
-    dependencies:
-      '@babel/types': 7.23.3
-      '@rollup/pluginutils': 5.0.5(rollup@4.6.1)
-      '@vue/compiler-sfc': 3.3.9
-      ast-kit: 0.11.2(rollup@4.6.1)
-      local-pkg: 0.5.0
-      magic-string-ast: 0.3.0
-      vue: 3.3.9(typescript@5.3.2)
-    transitivePeerDependencies:
-      - rollup
-    dev: false
-
-  /@vue-macros/reactivity-transform@0.4.0(rollup@4.6.1)(vue@3.3.9):
-    resolution: {integrity: sha512-3DG+FWkIZe5xZJhIdxyieIYcDKJGC3aUab1JWtEOkS8Q21rLpu6VKUjV6TmB5LNyLSGVp+7de/87Ptd6C6RHOA==}
-    engines: {node: '>=16.14.0'}
-    peerDependencies:
-      vue: ^2.7.0 || ^3.2.25
-    dependencies:
-      '@babel/parser': 7.23.3
-      '@vue-macros/common': 1.9.0(rollup@4.6.1)(vue@3.3.9)
-      '@vue/compiler-core': 3.3.8
-      '@vue/shared': 3.3.8
-      magic-string: 0.30.5
-      unplugin: 1.5.1
-      vue: 3.3.9(typescript@5.3.2)
-    transitivePeerDependencies:
-      - rollup
-    dev: false
-
-  /@vue/compiler-core@3.3.8:
-    resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==}
-    dependencies:
-      '@babel/parser': 7.23.3
-      '@vue/shared': 3.3.8
-      estree-walker: 2.0.2
-      source-map-js: 1.0.2
-    dev: false
-
   /@vue/compiler-core@3.3.9:
     resolution: {integrity: sha512-+/Lf68Vr/nFBA6ol4xOtJrW+BQWv3QWKfRwGSm70jtXwfhZNF4R/eRgyVJYoxFRhdCTk/F6g99BP0ffPgZihfQ==}
     dependencies:
@@ -8862,10 +8806,6 @@ packages:
       '@vue/shared': 3.3.9
       vue: 3.3.9(typescript@5.3.2)
 
-  /@vue/shared@3.3.8:
-    resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==}
-    dev: false
-
   /@vue/shared@3.3.9:
     resolution: {integrity: sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA==}
 
@@ -9344,17 +9284,6 @@ packages:
     resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
     dev: true
 
-  /ast-kit@0.11.2(rollup@4.6.1):
-    resolution: {integrity: sha512-Q0DjXK4ApbVoIf9GLyCo252tUH44iTnD/hiJ2TQaJeydYWSpKk0sI34+WMel8S9Wt5pbLgG02oJ+gkgX5DV3sQ==}
-    engines: {node: '>=16.14.0'}
-    dependencies:
-      '@babel/parser': 7.23.3
-      '@rollup/pluginutils': 5.0.5(rollup@4.6.1)
-      pathe: 1.1.1
-    transitivePeerDependencies:
-      - rollup
-    dev: false
-
   /ast-types@0.14.2:
     resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==}
     engines: {node: '>=4'}
@@ -9510,7 +9439,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@babel/template': 7.22.5
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
       '@types/babel__core': 7.20.0
       '@types/babel__traverse': 7.20.0
     dev: true
@@ -13658,7 +13587,7 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       '@babel/core': 7.22.11
-      '@babel/parser': 7.23.0
+      '@babel/parser': 7.23.3
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.0
       semver: 6.3.1
@@ -13671,7 +13600,7 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       '@babel/core': 7.22.11
-      '@babel/parser': 7.23.0
+      '@babel/parser': 7.23.3
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.0
       semver: 7.5.4
@@ -14604,14 +14533,6 @@ packages:
     engines: {node: '>=14'}
     dev: true
 
-  /local-pkg@0.5.0:
-    resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==}
-    engines: {node: '>=14'}
-    dependencies:
-      mlly: 1.4.2
-      pkg-types: 1.0.3
-    dev: false
-
   /locate-path@3.0.0:
     resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==}
     engines: {node: '>=6'}
@@ -14756,13 +14677,6 @@ packages:
     hasBin: true
     dev: true
 
-  /magic-string-ast@0.3.0:
-    resolution: {integrity: sha512-0shqecEPgdFpnI3AP90epXyxZy9g6CRZ+SZ7BcqFwYmtFEnZ1jpevcV5HoyVnlDS9gCnc1UIg3Rsvp3Ci7r8OA==}
-    engines: {node: '>=16.14.0'}
-    dependencies:
-      magic-string: 0.30.5
-    dev: false
-
   /magic-string@0.27.0:
     resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==}
     engines: {node: '>=12'}
@@ -15160,15 +15074,7 @@ packages:
       pathe: 1.1.1
       pkg-types: 1.0.3
       ufo: 1.1.2
-
-  /mlly@1.4.2:
-    resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==}
-    dependencies:
-      acorn: 8.11.2
-      pathe: 1.1.1
-      pkg-types: 1.0.3
-      ufo: 1.3.2
-    dev: false
+    dev: true
 
   /mnemonist@0.39.5:
     resolution: {integrity: sha512-FPUtkhtJ0efmEFGpU14x7jGbTB+s18LrzRL2KgoWz9YvcY3cPomz8tih01GbHwnGk/OmkOKfqd/RAQoc8Lm7DQ==}
@@ -16075,6 +15981,7 @@ packages:
 
   /pathe@1.1.1:
     resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
+    dev: true
 
   /pathval@1.1.1:
     resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==}
@@ -16272,6 +16179,7 @@ packages:
       jsonc-parser: 3.2.0
       mlly: 1.4.0
       pathe: 1.1.1
+    dev: true
 
   /plimit-lit@1.5.0:
     resolution: {integrity: sha512-Eb/MqCb1Iv/ok4m1FqIXqvUKPISufcjZ605hl3KM/n8GaX8zfhtgdLwZU3vKjuHGh2O9Rjog/bHTq8ofIShdng==}
@@ -17147,7 +17055,7 @@ packages:
     dependencies:
       '@babel/core': 7.22.11
       '@babel/traverse': 7.22.11
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
       '@types/babel__core': 7.20.0
       '@types/babel__traverse': 7.20.0
       '@types/doctrine': 0.0.9
@@ -19250,10 +19158,7 @@ packages:
 
   /ufo@1.1.2:
     resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
-
-  /ufo@1.3.2:
-    resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
-    dev: false
+    dev: true
 
   /uglify-js@3.17.4:
     resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==}
@@ -19396,13 +19301,14 @@ packages:
     resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
     engines: {node: '>= 0.8'}
 
-  /unplugin@1.5.1:
-    resolution: {integrity: sha512-0QkvG13z6RD+1L1FoibQqnvTwVBXvS4XSPwAyinVgoOCl2jAgwzdUKmEj05o4Lt8xwQI85Hb6mSyYkcAGwZPew==}
+  /unplugin@1.4.0:
+    resolution: {integrity: sha512-5x4eIEL6WgbzqGtF9UV8VEC/ehKptPXDS6L2b0mv4FRMkJxRtjaJfOWDd6a8+kYbqsjklix7yWP0N3SUepjXcg==}
     dependencies:
       acorn: 8.11.2
       chokidar: 3.5.3
       webpack-sources: 3.2.3
-      webpack-virtual-modules: 0.6.0
+      webpack-virtual-modules: 0.5.0
+    dev: true
 
   /untildify@4.0.0:
     resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==}
@@ -19761,7 +19667,7 @@ packages:
     resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==}
     dependencies:
       '@babel/parser': 7.23.3
-      '@babel/types': 7.23.3
+      '@babel/types': 7.22.17
       '@vue/compiler-dom': 3.3.9
       '@vue/compiler-sfc': 3.3.9
       ast-types: 0.14.2
@@ -19922,9 +19828,11 @@ 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.6.0:
-    resolution: {integrity: sha512-KnaMTE6EItz/f2q4Gwg5/rmeKVi79OR58NoYnwDJqCk9ywMtTGbBnBcfoBtN4QbYu0lWXvyMoH2Owxuhe4qI6Q==}
+  /webpack-virtual-modules@0.5.0:
+    resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
+    dev: true
 
   /whatwg-encoding@2.0.0:
     resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}