From d30ddd4c2ebcacc0d0b49c74e8dfe05b5422ba2e Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Sun, 9 Mar 2025 12:34:08 +0900
Subject: Refine preferences (#15597)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* wip

* wip

* wip

* test

* wip rollup pluginでsearchIndexの情報生成

* wip

* SPDX

* wip: markerIdを自動付与

* rollupでビルド時・devモード時に毎回uuidを生成するように

* 開発サーバーでだけ必要な挙動は開発サーバーのみで

* 条件が逆

* wip: childrenの生成

* update comment

* update comment

* rename auto generated file

* hashをパスと行数から決定

* Update privacy.vue

* Update privacy.vue

* wip

* Update general.vue

* Update general.vue

* wip

* wip

* Update SearchMarker.vue

* wip

* Update profile.vue

* Update mute-block.vue

* Update mute-block.vue

* Update general.vue

* Update general.vue

* childrenがduplicate key errorを吐く問題をいったん解決

* マーカーの形を成形

* loggerを置きかえ

* とりあえず省略記法に対応

* Refactor and Format codes

* wip

* Update settings-search-index.ts

* wip

* wip

* とりあえず不確定要因の仮置きidを削除

* hashの生成を正規化（絶対パスになっていたのを緩和）

* pathの入力を省略可能に

* adminでもパス生成できるように

* Update settings-search-index.ts

* Update privacy.vue

* wip

* build searchIndex

* wip

* build

* Update general.vue

* build

* Update sounds.vue

* build

* build

* Update sounds.vue

* 🎨

* 🎨

* Update privacy.vue

* Update privacy.vue

* Update security.vue

* create-search-indexを多少改善

* build

* Update 2fa.vue

* wip

* 必ずtransformCodeCacheを利用するように, キャッシュの明確な受け渡しを定義

* キャッシュはdevServerでなくても更新

* Revert "wip"

This reverts commit 41bffd3a13f55618bf939dc1c9acb2a77ead4054.

* inlining

* wip

* Update theme.vue

* 🎨

* wip normalize

* Update theme.vue

* キャッシュのパス変換

* build

* wip

* wip

* Update SearchMarker.vue

* i18n.ts['key'] の形式が取り出せない問題のFix

* build

* 仮でpath入れ

* 必ず絶対パスが使われるように

* wip

* 🎨

* storybookビルド時はcreateSearchIndexをしない

* inliningの構造化

* format code

* Update index.vue

* wip

* wip

* 🎨

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* clean up

* wip

* wip

* wip

* Update rollup-plugin-unwind-css-module-class-name.test.ts

* Update navbar.vue

* clean up

* wip

* wip

* wip

* wip

* wip

* Update preferences-backups.vue

* Update common.ts

* Update preferences.ts

* wip

* wip

* wip

* wip

* Update MkPreferenceContainer.vue

* Update MkPreferenceContainer.vue

* Update MkPreferenceContainer.vue

* enhance: 検索で上下矢印を使用することで検索結果を移動できるように

* Update main-boot.ts

* refactor

* wip

* Update sounds.vue

* fix(frontend): PageWindowでSearchMarkerが動作するように

* enhance(frontend): SearchMarkerの点滅を一定時間で止める

* wip

* lint fix

* fix: 子要素監視が抜けていたのを修正

* アニメーションの回数はCSSで制御するように

* refactor

* enhance(frontend): 検索インデックス作成時のログを削減

* revert

* fix

* fix

* Update preferences.ts

* Update preferences.ts

* wip

* Update preferences.ts

* wip

* 🎨

* wip

* Update MkPreferenceContainer.vue

* wip

* Update preferences.ts

* wip

* Update preferences.ts

* Update preferences.ts

* wip

* wip

* Update preferences.ts

* wip

* wip

* Update preferences.ts

* Update CHANGELOG.md

* Update preferences.ts

* Update deck-store.ts

* deckStoreをdefaultStoreに統合

* wip

* defaultStore -> store

* Update profile.ts

* wip

* refactor

* wip: plugin

* plugin

* plugin

* plugin

* Update plugin.ts

* wip

* Update plugin.vue

* Update preferences.ts

* Update main-boot.ts

* wip

* fix test

* Update plugin.vue

* Update plugin.vue

* Update utility.ts

* wip

* wip

* Update utility.ts

* wip

* wip

* clean up

* Update utility.ts

---------

Co-authored-by: tai-cha <dev@taichan.site>
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
---
 packages/frontend/src/components/MkUserSetupDialog.vue | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

(limited to 'packages/frontend/src/components/MkUserSetupDialog.vue')

diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index b7261129ef..cba6f6fd90 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -139,7 +139,7 @@ import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import { host } from '@@/js/config.js';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
-import { defaultStore } from '@/store.js';
+import { store } from '@/store.js';
 import * as os from '@/os.js';
 
 const emit = defineEmits<{
@@ -149,10 +149,10 @@ const emit = defineEmits<{
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
 // eslint-disable-next-line vue/no-setup-props-reactivity-loss
-const page = ref(defaultStore.state.accountSetupWizard);
+const page = ref(store.state.accountSetupWizard);
 
 watch(page, () => {
-	defaultStore.set('accountSetupWizard', page.value);
+	store.set('accountSetupWizard', page.value);
 });
 
 async function close(skip: boolean) {
@@ -165,11 +165,11 @@ async function close(skip: boolean) {
 	}
 
 	dialog.value?.close();
-	defaultStore.set('accountSetupWizard', -1);
+	store.set('accountSetupWizard', -1);
 }
 
 function setupComplete() {
-	defaultStore.set('accountSetupWizard', -1);
+	store.set('accountSetupWizard', -1);
 	dialog.value?.close();
 }
 
@@ -194,7 +194,7 @@ async function later(later: boolean) {
 	}
 
 	dialog.value?.close();
-	defaultStore.set('accountSetupWizard', 0);
+	store.set('accountSetupWizard', 0);
 }
 </script>
 
-- 
cgit v1.2.3-freya


From 08f7e7d9b35ed384671b1918c4b6d16fd1181f82 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Mon, 10 Mar 2025 10:51:54 +0900
Subject: refactor(frontend): rename pizzax fields

---
 ...lup-plugin-unwind-css-module-class-name.test.ts |   4 +-
 packages/frontend/src/boot/common.ts               |  12 +-
 packages/frontend/src/boot/main-boot.ts            | 148 ++++++++++-----------
 .../frontend/src/components/MkAutocomplete.vue     |   6 +-
 packages/frontend/src/components/MkCaptcha.vue     |   2 +-
 packages/frontend/src/components/MkChart.vue       |   2 +-
 packages/frontend/src/components/MkCode.core.vue   |   2 +-
 packages/frontend/src/components/MkEmojiPicker.vue |  10 +-
 .../frontend/src/components/MkFollowButton.vue     |   4 +-
 packages/frontend/src/components/MkHeatmap.vue     |   2 +-
 packages/frontend/src/components/MkPostForm.vue    |   8 +-
 .../frontend/src/components/MkRetentionHeatmap.vue |   2 +-
 .../src/components/MkRetentionLineChart.vue        |   2 +-
 packages/frontend/src/components/MkUrlPreview.vue  |   2 +-
 .../frontend/src/components/MkUserSelectDialog.vue |   4 +-
 .../frontend/src/components/MkUserSetupDialog.vue  |   2 +-
 .../MkVisitorDashboard.ActiveUsersChart.vue        |   2 +-
 packages/frontend/src/components/global/MkAd.vue   |   4 +-
 .../src/components/global/MkFooterSpacer.vue       |   2 +-
 packages/frontend/src/deck.ts                      |  92 ++++++-------
 packages/frontend/src/pages/about-misskey.vue      |   2 +-
 packages/frontend/src/pages/admin/abuses.vue       |   2 +-
 .../src/pages/admin/overview.active-users.vue      |   2 +-
 .../src/pages/admin/overview.ap-requests.vue       |   2 +-
 .../src/pages/admin/overview.queue.chart.vue       |   2 +-
 .../frontend/src/pages/admin/queue.chart.chart.vue |   2 +-
 .../frontend/src/pages/drop-and-fusion.game.vue    |   2 +-
 .../frontend/src/pages/settings/emoji-picker.vue   |   4 +-
 .../frontend/src/pages/settings/import-export.vue  |   2 +-
 packages/frontend/src/pages/settings/index.vue     |   2 +-
 .../frontend/src/pages/settings/preferences.vue    |   8 +-
 packages/frontend/src/pages/theme-editor.vue       |   2 +-
 packages/frontend/src/pages/timeline.vue           |  24 ++--
 .../frontend/src/pages/user/activity.following.vue |   2 +-
 .../frontend/src/pages/user/activity.notes.vue     |   2 +-
 packages/frontend/src/pages/user/activity.pv.vue   |   2 +-
 packages/frontend/src/pizzax.ts                    |  45 ++++---
 packages/frontend/src/plugin.ts                    |  12 +-
 packages/frontend/src/preferences.ts               |   2 +-
 packages/frontend/src/preferences/store.ts         |   2 +-
 packages/frontend/src/preferences/utility.ts       |   2 +-
 packages/frontend/src/ui/_common_/navbar.vue       |   4 +-
 packages/frontend/src/ui/classic.sidebar.vue       |   2 +-
 packages/frontend/src/ui/classic.vue               |   4 +-
 packages/frontend/src/ui/deck.vue                  |  18 +--
 packages/frontend/src/utility/emoji-picker.ts      |   2 +-
 packages/frontend/src/utility/get-note-menu.ts     |   4 +-
 packages/frontend/src/utility/init-chart.ts        |   2 +-
 packages/frontend/src/utility/reaction-picker.ts   |   2 +-
 packages/frontend/src/widgets/WidgetMemo.vue       |   4 +-
 50 files changed, 243 insertions(+), 236 deletions(-)

(limited to 'packages/frontend/src/components/MkUserSetupDialog.vue')

diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
index 884ae3afac..ccfa08575b 100644
--- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
@@ -74,7 +74,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
     let fetching = ref(true);
     let images = ref([]);
     function thumbnail(image) {
-      return store.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
+      return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
     }
     onMounted(() => {
       const image = [
@@ -190,7 +190,7 @@ const index_photos = defineComponent({
     let fetching = ref(true);
     let images = ref([]);
     function thumbnail(image) {
-      return store.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
+      return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
     }
     onMounted(() => {
       const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"];
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index efada79823..d09c6d39d2 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -154,26 +154,26 @@ export async function common(createVue: () => App<Element>) {
 	//#endregion
 
 	// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
-	watch(store.reactiveState.darkMode, (darkMode) => {
+	watch(store.r.darkMode, (darkMode) => {
 		applyTheme(darkMode
 			? (prefer.s.darkTheme ?? defaultDarkTheme)
 			: (prefer.s.lightTheme ?? defaultLightTheme),
 		);
 	}, { immediate: miLocalStorage.getItem('theme') == null });
 
-	document.documentElement.dataset.colorScheme = store.state.darkMode ? 'dark' : 'light';
+	document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
 
 	const darkTheme = prefer.model('darkTheme');
 	const lightTheme = prefer.model('lightTheme');
 
 	watch(darkTheme, (theme) => {
-		if (store.state.darkMode) {
+		if (store.s.darkMode) {
 			applyTheme(theme ?? defaultDarkTheme);
 		}
 	});
 
 	watch(lightTheme, (theme) => {
-		if (!store.state.darkMode) {
+		if (!store.s.darkMode) {
 			applyTheme(theme ?? defaultLightTheme);
 		}
 	});
@@ -190,9 +190,9 @@ export async function common(createVue: () => App<Element>) {
 	});
 	//#endregion
 
-	if (prefer.s.darkTheme && store.state.darkMode) {
+	if (prefer.s.darkTheme && store.s.darkMode) {
 		if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme);
-	} else if (prefer.s.lightTheme && !store.state.darkMode) {
+	} else if (prefer.s.lightTheme && !store.s.darkMode) {
 		if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme);
 	}
 
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index a64e14cc1a..411cf3f672 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -138,7 +138,7 @@ export async function mainBoot() {
 		store.loaded.then(async () => {
 			// prefereces migration
 			// TODO: そのうち消す
-			if (store.state.menu.length > 0) {
+			if (store.s.menu.length > 0) {
 				const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []);
 				if (themes.length > 0) {
 					prefer.set('themes', themes);
@@ -152,84 +152,84 @@ export async function mainBoot() {
 				prefer.set('lightTheme', ColdDeviceStorage.get('lightTheme'));
 				prefer.set('darkTheme', ColdDeviceStorage.get('darkTheme'));
 				prefer.set('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode'));
-				prefer.set('overridedDeviceKind', store.state.overridedDeviceKind);
-				prefer.set('widgets', store.state.widgets);
-				prefer.set('keepCw', store.state.keepCw);
-				prefer.set('collapseRenotes', store.state.collapseRenotes);
-				prefer.set('rememberNoteVisibility', store.state.rememberNoteVisibility);
-				prefer.set('uploadFolder', store.state.uploadFolder);
-				prefer.set('keepOriginalUploading', store.state.keepOriginalUploading);
-				prefer.set('menu', store.state.menu);
-				prefer.set('statusbars', store.state.statusbars);
-				prefer.set('pinnedUserLists', store.state.pinnedUserLists);
-				prefer.set('serverDisconnectedBehavior', store.state.serverDisconnectedBehavior);
-				prefer.set('nsfw', store.state.nsfw);
-				prefer.set('highlightSensitiveMedia', store.state.highlightSensitiveMedia);
-				prefer.set('animation', store.state.animation);
-				prefer.set('animatedMfm', store.state.animatedMfm);
-				prefer.set('advancedMfm', store.state.advancedMfm);
-				prefer.set('showReactionsCount', store.state.showReactionsCount);
-				prefer.set('enableQuickAddMfmFunction', store.state.enableQuickAddMfmFunction);
-				prefer.set('loadRawImages', store.state.loadRawImages);
-				prefer.set('imageNewTab', store.state.imageNewTab);
-				prefer.set('disableShowingAnimatedImages', store.state.disableShowingAnimatedImages);
-				prefer.set('emojiStyle', store.state.emojiStyle);
-				prefer.set('menuStyle', store.state.menuStyle);
-				prefer.set('useBlurEffectForModal', store.state.useBlurEffectForModal);
-				prefer.set('useBlurEffect', store.state.useBlurEffect);
-				prefer.set('showFixedPostForm', store.state.showFixedPostForm);
-				prefer.set('showFixedPostFormInChannel', store.state.showFixedPostFormInChannel);
-				prefer.set('enableInfiniteScroll', store.state.enableInfiniteScroll);
-				prefer.set('useReactionPickerForContextMenu', store.state.useReactionPickerForContextMenu);
-				prefer.set('showGapBetweenNotesInTimeline', store.state.showGapBetweenNotesInTimeline);
-				prefer.set('instanceTicker', store.state.instanceTicker);
-				prefer.set('emojiPickerScale', store.state.emojiPickerScale);
-				prefer.set('emojiPickerWidth', store.state.emojiPickerWidth);
-				prefer.set('emojiPickerHeight', store.state.emojiPickerHeight);
-				prefer.set('emojiPickerStyle', store.state.emojiPickerStyle);
-				prefer.set('reportError', store.state.reportError);
-				prefer.set('squareAvatars', store.state.squareAvatars);
-				prefer.set('showAvatarDecorations', store.state.showAvatarDecorations);
-				prefer.set('numberOfPageCache', store.state.numberOfPageCache);
-				prefer.set('showNoteActionsOnlyHover', store.state.showNoteActionsOnlyHover);
-				prefer.set('showClipButtonInNoteFooter', store.state.showClipButtonInNoteFooter);
-				prefer.set('reactionsDisplaySize', store.state.reactionsDisplaySize);
-				prefer.set('limitWidthOfReaction', store.state.limitWidthOfReaction);
-				prefer.set('forceShowAds', store.state.forceShowAds);
-				prefer.set('aiChanMode', store.state.aiChanMode);
-				prefer.set('devMode', store.state.devMode);
-				prefer.set('mediaListWithOneImageAppearance', store.state.mediaListWithOneImageAppearance);
-				prefer.set('notificationPosition', store.state.notificationPosition);
-				prefer.set('notificationStackAxis', store.state.notificationStackAxis);
-				prefer.set('enableCondensedLine', store.state.enableCondensedLine);
-				prefer.set('keepScreenOn', store.state.keepScreenOn);
-				prefer.set('disableStreamingTimeline', store.state.disableStreamingTimeline);
-				prefer.set('useGroupedNotifications', store.state.useGroupedNotifications);
-				prefer.set('dataSaver', store.state.dataSaver);
-				prefer.set('enableSeasonalScreenEffect', store.state.enableSeasonalScreenEffect);
-				prefer.set('enableHorizontalSwipe', store.state.enableHorizontalSwipe);
-				prefer.set('useNativeUiForVideoAudioPlayer', store.state.useNativeUIForVideoAudioPlayer);
-				prefer.set('keepOriginalFilename', store.state.keepOriginalFilename);
-				prefer.set('alwaysConfirmFollow', store.state.alwaysConfirmFollow);
-				prefer.set('confirmWhenRevealingSensitiveMedia', store.state.confirmWhenRevealingSensitiveMedia);
-				prefer.set('contextMenu', store.state.contextMenu);
-				prefer.set('skipNoteRender', store.state.skipNoteRender);
-				prefer.set('showSoftWordMutedWord', store.state.showSoftWordMutedWord);
-				prefer.set('confirmOnReact', store.state.confirmOnReact);
-				prefer.set('sound.masterVolume', store.state.sound_masterVolume);
-				prefer.set('sound.notUseSound', store.state.sound_notUseSound);
-				prefer.set('sound.useSoundOnlyWhenActive', store.state.sound_useSoundOnlyWhenActive);
-				prefer.set('sound.on.note', store.state.sound_note as any);
-				prefer.set('sound.on.noteMy', store.state.sound_noteMy as any);
-				prefer.set('sound.on.notification', store.state.sound_notification as any);
-				prefer.set('sound.on.reaction', store.state.sound_reaction as any);
+				prefer.set('overridedDeviceKind', store.s.overridedDeviceKind);
+				prefer.set('widgets', store.s.widgets);
+				prefer.set('keepCw', store.s.keepCw);
+				prefer.set('collapseRenotes', store.s.collapseRenotes);
+				prefer.set('rememberNoteVisibility', store.s.rememberNoteVisibility);
+				prefer.set('uploadFolder', store.s.uploadFolder);
+				prefer.set('keepOriginalUploading', store.s.keepOriginalUploading);
+				prefer.set('menu', store.s.menu);
+				prefer.set('statusbars', store.s.statusbars);
+				prefer.set('pinnedUserLists', store.s.pinnedUserLists);
+				prefer.set('serverDisconnectedBehavior', store.s.serverDisconnectedBehavior);
+				prefer.set('nsfw', store.s.nsfw);
+				prefer.set('highlightSensitiveMedia', store.s.highlightSensitiveMedia);
+				prefer.set('animation', store.s.animation);
+				prefer.set('animatedMfm', store.s.animatedMfm);
+				prefer.set('advancedMfm', store.s.advancedMfm);
+				prefer.set('showReactionsCount', store.s.showReactionsCount);
+				prefer.set('enableQuickAddMfmFunction', store.s.enableQuickAddMfmFunction);
+				prefer.set('loadRawImages', store.s.loadRawImages);
+				prefer.set('imageNewTab', store.s.imageNewTab);
+				prefer.set('disableShowingAnimatedImages', store.s.disableShowingAnimatedImages);
+				prefer.set('emojiStyle', store.s.emojiStyle);
+				prefer.set('menuStyle', store.s.menuStyle);
+				prefer.set('useBlurEffectForModal', store.s.useBlurEffectForModal);
+				prefer.set('useBlurEffect', store.s.useBlurEffect);
+				prefer.set('showFixedPostForm', store.s.showFixedPostForm);
+				prefer.set('showFixedPostFormInChannel', store.s.showFixedPostFormInChannel);
+				prefer.set('enableInfiniteScroll', store.s.enableInfiniteScroll);
+				prefer.set('useReactionPickerForContextMenu', store.s.useReactionPickerForContextMenu);
+				prefer.set('showGapBetweenNotesInTimeline', store.s.showGapBetweenNotesInTimeline);
+				prefer.set('instanceTicker', store.s.instanceTicker);
+				prefer.set('emojiPickerScale', store.s.emojiPickerScale);
+				prefer.set('emojiPickerWidth', store.s.emojiPickerWidth);
+				prefer.set('emojiPickerHeight', store.s.emojiPickerHeight);
+				prefer.set('emojiPickerStyle', store.s.emojiPickerStyle);
+				prefer.set('reportError', store.s.reportError);
+				prefer.set('squareAvatars', store.s.squareAvatars);
+				prefer.set('showAvatarDecorations', store.s.showAvatarDecorations);
+				prefer.set('numberOfPageCache', store.s.numberOfPageCache);
+				prefer.set('showNoteActionsOnlyHover', store.s.showNoteActionsOnlyHover);
+				prefer.set('showClipButtonInNoteFooter', store.s.showClipButtonInNoteFooter);
+				prefer.set('reactionsDisplaySize', store.s.reactionsDisplaySize);
+				prefer.set('limitWidthOfReaction', store.s.limitWidthOfReaction);
+				prefer.set('forceShowAds', store.s.forceShowAds);
+				prefer.set('aiChanMode', store.s.aiChanMode);
+				prefer.set('devMode', store.s.devMode);
+				prefer.set('mediaListWithOneImageAppearance', store.s.mediaListWithOneImageAppearance);
+				prefer.set('notificationPosition', store.s.notificationPosition);
+				prefer.set('notificationStackAxis', store.s.notificationStackAxis);
+				prefer.set('enableCondensedLine', store.s.enableCondensedLine);
+				prefer.set('keepScreenOn', store.s.keepScreenOn);
+				prefer.set('disableStreamingTimeline', store.s.disableStreamingTimeline);
+				prefer.set('useGroupedNotifications', store.s.useGroupedNotifications);
+				prefer.set('dataSaver', store.s.dataSaver);
+				prefer.set('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect);
+				prefer.set('enableHorizontalSwipe', store.s.enableHorizontalSwipe);
+				prefer.set('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer);
+				prefer.set('keepOriginalFilename', store.s.keepOriginalFilename);
+				prefer.set('alwaysConfirmFollow', store.s.alwaysConfirmFollow);
+				prefer.set('confirmWhenRevealingSensitiveMedia', store.s.confirmWhenRevealingSensitiveMedia);
+				prefer.set('contextMenu', store.s.contextMenu);
+				prefer.set('skipNoteRender', store.s.skipNoteRender);
+				prefer.set('showSoftWordMutedWord', store.s.showSoftWordMutedWord);
+				prefer.set('confirmOnReact', store.s.confirmOnReact);
+				prefer.set('sound.masterVolume', store.s.sound_masterVolume);
+				prefer.set('sound.notUseSound', store.s.sound_notUseSound);
+				prefer.set('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive);
+				prefer.set('sound.on.note', store.s.sound_note as any);
+				prefer.set('sound.on.noteMy', store.s.sound_noteMy as any);
+				prefer.set('sound.on.notification', store.s.sound_notification as any);
+				prefer.set('sound.on.reaction', store.s.sound_reaction as any);
 				store.set('deck.profile', deckStore.state.profile);
 				store.set('deck.columns', deckStore.state.columns);
 				store.set('deck.layout', deckStore.state.layout);
 				store.set('menu', []);
 			}
 
-			if (store.state.accountSetupWizard !== -1) {
+			if (store.s.accountSetupWizard !== -1) {
 				const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {
 					closed: () => dispose(),
 				});
@@ -502,7 +502,7 @@ export async function mainBoot() {
 			post();
 		},
 		'd': () => {
-			store.set('darkMode', !store.state.darkMode);
+			store.set('darkMode', !store.s.darkMode);
 		},
 		's': () => {
 			mainRouter.push('/search');
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 912e2d65a5..7436d4f1b2 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -73,7 +73,7 @@ const emojiDb = computed(() => {
 		url: char2path(x.char),
 	}));
 
-	for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) {
+	for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
 		for (const [emoji, keywords] of Object.entries(index)) {
 			for (const k of keywords) {
 				unicodeEmojiDB.push({
@@ -155,7 +155,7 @@ function complete(type: string, value: any) {
 	emit('done', { type, value });
 	emit('closed');
 	if (type === 'emoji') {
-		let recents = store.state.recentlyUsedEmojis;
+		let recents = store.s.recentlyUsedEmojis;
 		recents = recents.filter((emoji: any) => emoji !== value);
 		recents.unshift(value);
 		store.set('recentlyUsedEmojis', recents.splice(0, 32));
@@ -238,7 +238,7 @@ function exec() {
 	} else if (props.type === 'emoji') {
 		if (!props.q || props.q === '') {
 			// 最近使った絵文字をサジェスト
-			emojis.value = store.state.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
+			emojis.value = store.s.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[];
 			return;
 		}
 
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index 05b8264a83..30eafb7a5b 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -154,7 +154,7 @@ async function requestRender() {
 
 		captchaWidgetId.value = captcha.value.render(elem, {
 			sitekey: props.sitekey,
-			theme: store.state.darkMode ? 'dark' : 'light',
+			theme: store.s.darkMode ? 'dark' : 'light',
 			callback: callback,
 			'expired-callback': () => callback(undefined),
 			'error-callback': () => callback(undefined),
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index 4707d02ce9..c1a55906ae 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -161,7 +161,7 @@ const render = () => {
 		chartInstance.destroy();
 	}
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y)));
 
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index 948419130e..8b39468d4c 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -22,7 +22,7 @@ const props = defineProps<{
 }>();
 
 const highlighter = await getHighlighter();
-const darkMode = store.reactiveState.darkMode;
+const darkMode = store.r.darkMode;
 const codeLang = ref<BundledLanguage | 'aiscript'>('js');
 
 const [lightThemeName, darkThemeName] = await Promise.all([
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 214b244802..384682277e 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -166,7 +166,7 @@ const {
 	emojiPickerHeight,
 } = prefer.r;
 
-const recentlyUsedEmojis = store.reactiveState.recentlyUsedEmojis;
+const recentlyUsedEmojis = store.r.recentlyUsedEmojis;
 
 const recentlyUsedEmojisDef = computed(() => {
 	return recentlyUsedEmojis.value.map(getDef);
@@ -319,7 +319,7 @@ watch(q, () => {
 			}
 			if (matches.size >= max) return matches;
 
-			for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) {
+			for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
 				for (const emoji of emojis) {
 					if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) {
 						matches.add(emoji);
@@ -336,7 +336,7 @@ watch(q, () => {
 			}
 			if (matches.size >= max) return matches;
 
-			for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) {
+			for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
 				for (const emoji of emojis) {
 					if (index[emoji.char].some(k => k.startsWith(newQ))) {
 						matches.add(emoji);
@@ -353,7 +353,7 @@ watch(q, () => {
 			}
 			if (matches.size >= max) return matches;
 
-			for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) {
+			for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) {
 				for (const emoji of emojis) {
 					if (index[emoji.char].some(k => k.includes(newQ))) {
 						matches.add(emoji);
@@ -429,7 +429,7 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef,
 
 	// 最近使った絵文字更新
 	if (!pinned.value?.includes(key)) {
-		let recents = store.state.recentlyUsedEmojis;
+		let recents = store.s.recentlyUsedEmojis;
 		recents = recents.filter((emoji) => emoji !== key);
 		recents.unshift(key);
 		store.set('recentlyUsedEmojis', recents.splice(0, 32));
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index 64828699d6..3d5d0ec5ab 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -121,11 +121,11 @@ async function onClick() {
 			} else {
 				await misskeyApi('following/create', {
 					userId: props.user.id,
-					withReplies: store.state.defaultWithReplies,
+					withReplies: store.s.defaultWithReplies,
 				});
 				emit('update:user', {
 					...props.user,
-					withReplies: store.state.defaultWithReplies,
+					withReplies: store.s.defaultWithReplies,
 				});
 				hasPendingFollowRequestFromYou.value = true;
 
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index 4c4ae86db3..8339e68b07 100644
--- a/packages/frontend/src/components/MkHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -106,7 +106,7 @@ async function renderChart() {
 
 	await nextTick();
 
-	const color = store.state.darkMode ? '#b4e900' : '#86b300';
+	const color = store.s.darkMode ? '#b4e900' : '#86b300';
 
 	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
 	const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 755e13279c..8dbb2a80d6 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -176,18 +176,18 @@ const text = ref(props.initialText ?? '');
 const files = ref(props.initialFiles ?? []);
 const poll = ref<PollEditorModelValue | null>(null);
 const useCw = ref<boolean>(!!props.initialCw);
-const showPreview = ref(store.state.showPreview);
+const showPreview = ref(store.s.showPreview);
 watch(showPreview, () => store.set('showPreview', showPreview.value));
 const showAddMfmFunction = ref(prefer.s.enableQuickAddMfmFunction);
 watch(showAddMfmFunction, () => prefer.set('enableQuickAddMfmFunction', showAddMfmFunction.value));
 const cw = ref<string | null>(props.initialCw ?? null);
-const localOnly = ref(props.initialLocalOnly ?? (prefer.s.rememberNoteVisibility ? store.state.localOnly : prefer.s.defaultNoteLocalOnly));
-const visibility = ref(props.initialVisibility ?? (prefer.s.rememberNoteVisibility ? store.state.visibility : prefer.s.defaultNoteVisibility));
+const localOnly = ref(props.initialLocalOnly ?? (prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly));
+const visibility = ref(props.initialVisibility ?? (prefer.s.rememberNoteVisibility ? store.s.visibility : prefer.s.defaultNoteVisibility));
 const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]);
 if (props.initialVisibleUsers) {
 	props.initialVisibleUsers.forEach(u => pushVisibleUser(u));
 }
-const reactionAcceptance = ref(store.state.reactionAcceptance);
+const reactionAcceptance = ref(store.s.reactionAcceptance);
 const draghover = ref(false);
 const quoteId = ref<string | null>(null);
 const hasNotSpecifiedMentions = ref(false);
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 1dc1da0bc3..c53bf98f67 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -75,7 +75,7 @@ async function renderChart() {
 
 	await nextTick();
 
-	const color = store.state.darkMode ? '#b4e900' : '#86b300';
+	const color = store.s.darkMode ? '#b4e900' : '#86b300';
 
 	const getYYYYMMDD = (date: Date) => {
 		const y = date.getFullYear().toString().padStart(2, '0');
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index dd4a8a1a6d..9e03be3e7f 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -42,7 +42,7 @@ const getDate = (ymd: string) => {
 onMounted(async () => {
 	let raw = await misskeyApi('retention', { });
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent'));
 	const color = accent.toHex();
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index f8ac638e40..f20aee0ce3 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin"
 			scrolling="no"
 			:style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }"
-			:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${store.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"
+			:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${store.s.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"
 		></iframe>
 	</div>
 	<div :class="$style.action">
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 1dbc143f8f..e5c6df267b 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -128,7 +128,7 @@ async function ok() {
 	dialogEl.value?.close();
 
 	// 最近使ったユーザー更新
-	let recents = store.state.recentlyUsedUsers;
+	let recents = store.s.recentlyUsedUsers;
 	recents = recents.filter(x => x !== selected.value?.id);
 	recents.unshift(selected.value.id);
 	store.set('recentlyUsedUsers', recents.splice(0, 16));
@@ -141,7 +141,7 @@ function cancel() {
 
 onMounted(() => {
 	misskeyApi('users/show', {
-		userIds: store.state.recentlyUsedUsers,
+		userIds: store.s.recentlyUsedUsers,
 	}).then(foundUsers => {
 		let _users = foundUsers;
 		_users = _users.filter((u) => {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index cba6f6fd90..eb3a69217e 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -149,7 +149,7 @@ const emit = defineEmits<{
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
 // eslint-disable-next-line vue/no-setup-props-reactivity-loss
-const page = ref(store.state.accountSetupWizard);
+const page = ref(store.s.accountSetupWizard);
 
 watch(page, () => {
 	store.set('accountSetupWizard', page.value);
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
index 3aa76be0bc..872d4201bb 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -59,7 +59,7 @@ async function renderChart() {
 
 	await nextTick();
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const computedStyle = getComputedStyle(document.documentElement);
 	const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index 4f9bfd02bd..c196519c15 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -67,7 +67,7 @@ const choseAd = (): Ad | null => {
 		return props.specify;
 	}
 
-	const allAds = instance.ads.map(ad => store.state.mutedAds.includes(ad.id) ? {
+	const allAds = instance.ads.map(ad => store.s.mutedAds.includes(ad.id) ? {
 		...ad,
 		ratio: 0,
 	} : ad);
@@ -112,7 +112,7 @@ const shouldHide = ref(!prefer.s.forceShowAds && $i && $i.policies.canHideAds &&
 
 function reduceFrequency(): void {
 	if (chosen.value == null) return;
-	if (store.state.mutedAds.includes(chosen.value.id)) return;
+	if (store.s.mutedAds.includes(chosen.value.id)) return;
 	store.push('mutedAds', chosen.value.id);
 	os.success();
 	chosen.value = choseAd();
diff --git a/packages/frontend/src/components/global/MkFooterSpacer.vue b/packages/frontend/src/components/global/MkFooterSpacer.vue
index d08645aaf3..45b415a7f6 100644
--- a/packages/frontend/src/components/global/MkFooterSpacer.vue
+++ b/packages/frontend/src/components/global/MkFooterSpacer.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div :class="[$style.spacer, store.reactiveState.darkMode.value ? $style.dark : $style.light]"></div>
+<div :class="[$style.spacer, store.r.darkMode.value ? $style.dark : $style.light]"></div>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/frontend/src/deck.ts b/packages/frontend/src/deck.ts
index 5607b54515..f158db2f85 100644
--- a/packages/frontend/src/deck.ts
+++ b/packages/frontend/src/deck.ts
@@ -59,12 +59,12 @@ export const loadDeck = async () => {
 	try {
 		deck = await misskeyApi('i/registry/get', {
 			scope: ['client', 'deck', 'profiles'],
-			key: store.state['deck.profile'],
+			key: store.s['deck.profile'],
 		});
 	} catch (err) {
 		if (typeof err === 'object' && err != null && 'code' in err && err.code === 'NO_SUCH_KEY') {
 			// 後方互換性のため
-			if (store.state['deck.profile'] === 'default') {
+			if (store.s['deck.profile'] === 'default') {
 				saveDeck();
 				return;
 			}
@@ -83,10 +83,10 @@ export const loadDeck = async () => {
 export async function forceSaveDeck() {
 	await misskeyApi('i/registry/set', {
 		scope: ['client', 'deck', 'profiles'],
-		key: store.state['deck.profile'],
+		key: store.s['deck.profile'],
 		value: {
-			columns: store.reactiveState['deck.columns'].value,
-			layout: store.reactiveState['deck.layout'].value,
+			columns: store.r['deck.columns'].value,
+			layout: store.r['deck.layout'].value,
 		},
 	});
 }
@@ -117,19 +117,19 @@ export function addColumn(column: Column) {
 }
 
 export function removeColumn(id: Column['id']) {
-	store.set('deck.columns', store.state['deck.columns'].filter(c => c.id !== id));
-	store.set('deck.layout', store.state['deck.layout']
+	store.set('deck.columns', store.s['deck.columns'].filter(c => c.id !== id));
+	store.set('deck.layout', store.s['deck.layout']
 		.map(ids => ids.filter(_id => _id !== id))
 		.filter(ids => ids.length > 0));
 	saveDeck();
 }
 
 export function swapColumn(a: Column['id'], b: Column['id']) {
-	const aX = store.state['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1);
-	const aY = store.state['deck.layout'][aX].findIndex(id => id === a);
-	const bX = store.state['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1);
-	const bY = store.state['deck.layout'][bX].findIndex(id => id === b);
-	const layout = deepClone(store.state['deck.layout']);
+	const aX = store.s['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1);
+	const aY = store.s['deck.layout'][aX].findIndex(id => id === a);
+	const bX = store.s['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1);
+	const bY = store.s['deck.layout'][bX].findIndex(id => id === b);
+	const layout = deepClone(store.s['deck.layout']);
 	layout[aX][aY] = b;
 	layout[bX][bY] = a;
 	store.set('deck.layout', layout);
@@ -137,12 +137,12 @@ export function swapColumn(a: Column['id'], b: Column['id']) {
 }
 
 export function swapLeftColumn(id: Column['id']) {
-	const layout = deepClone(store.state['deck.layout']);
-	store.state['deck.layout'].some((ids, i) => {
+	const layout = deepClone(store.s['deck.layout']);
+	store.s['deck.layout'].some((ids, i) => {
 		if (ids.includes(id)) {
-			const left = store.state['deck.layout'][i - 1];
+			const left = store.s['deck.layout'][i - 1];
 			if (left) {
-				layout[i - 1] = store.state['deck.layout'][i];
+				layout[i - 1] = store.s['deck.layout'][i];
 				layout[i] = left;
 				store.set('deck.layout', layout);
 			}
@@ -154,12 +154,12 @@ export function swapLeftColumn(id: Column['id']) {
 }
 
 export function swapRightColumn(id: Column['id']) {
-	const layout = deepClone(store.state['deck.layout']);
-	store.state['deck.layout'].some((ids, i) => {
+	const layout = deepClone(store.s['deck.layout']);
+	store.s['deck.layout'].some((ids, i) => {
 		if (ids.includes(id)) {
-			const right = store.state['deck.layout'][i + 1];
+			const right = store.s['deck.layout'][i + 1];
 			if (right) {
-				layout[i + 1] = store.state['deck.layout'][i];
+				layout[i + 1] = store.s['deck.layout'][i];
 				layout[i] = right;
 				store.set('deck.layout', layout);
 			}
@@ -171,9 +171,9 @@ export function swapRightColumn(id: Column['id']) {
 }
 
 export function swapUpColumn(id: Column['id']) {
-	const layout = deepClone(store.state['deck.layout']);
-	const idsIndex = store.state['deck.layout'].findIndex(ids => ids.includes(id));
-	const ids = deepClone(store.state['deck.layout'][idsIndex]);
+	const layout = deepClone(store.s['deck.layout']);
+	const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id));
+	const ids = deepClone(store.s['deck.layout'][idsIndex]);
 	ids.some((x, i) => {
 		if (x === id) {
 			const up = ids[i - 1];
@@ -192,9 +192,9 @@ export function swapUpColumn(id: Column['id']) {
 }
 
 export function swapDownColumn(id: Column['id']) {
-	const layout = deepClone(store.state['deck.layout']);
-	const idsIndex = store.state['deck.layout'].findIndex(ids => ids.includes(id));
-	const ids = deepClone(store.state['deck.layout'][idsIndex]);
+	const layout = deepClone(store.s['deck.layout']);
+	const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id));
+	const ids = deepClone(store.s['deck.layout'][idsIndex]);
 	ids.some((x, i) => {
 		if (x === id) {
 			const down = ids[i + 1];
@@ -213,8 +213,8 @@ export function swapDownColumn(id: Column['id']) {
 }
 
 export function stackLeftColumn(id: Column['id']) {
-	let layout = deepClone(store.state['deck.layout']);
-	const i = store.state['deck.layout'].findIndex(ids => ids.includes(id));
+	let layout = deepClone(store.s['deck.layout']);
+	const i = store.s['deck.layout'].findIndex(ids => ids.includes(id));
 	layout = layout.map(ids => ids.filter(_id => _id !== id));
 	layout[i - 1].push(id);
 	layout = layout.filter(ids => ids.length > 0);
@@ -223,15 +223,15 @@ export function stackLeftColumn(id: Column['id']) {
 }
 
 export function popRightColumn(id: Column['id']) {
-	let layout = deepClone(store.state['deck.layout']);
-	const i = store.state['deck.layout'].findIndex(ids => ids.includes(id));
+	let layout = deepClone(store.s['deck.layout']);
+	const i = store.s['deck.layout'].findIndex(ids => ids.includes(id));
 	const affected = layout[i];
 	layout = layout.map(ids => ids.filter(_id => _id !== id));
 	layout.splice(i + 1, 0, [id]);
 	layout = layout.filter(ids => ids.length > 0);
 	store.set('deck.layout', layout);
 
-	const columns = deepClone(store.state['deck.columns']);
+	const columns = deepClone(store.s['deck.columns']);
 	for (const column of columns) {
 		if (affected.includes(column.id)) {
 			column.active = true;
@@ -243,9 +243,9 @@ export function popRightColumn(id: Column['id']) {
 }
 
 export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
-	const columns = deepClone(store.state['deck.columns']);
-	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id);
-	const column = deepClone(store.state['deck.columns'][columnIndex]);
+	const columns = deepClone(store.s['deck.columns']);
+	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
+	const column = deepClone(store.s['deck.columns'][columnIndex]);
 	if (column == null) return;
 	if (column.widgets == null) column.widgets = [];
 	column.widgets.unshift(widget);
@@ -255,9 +255,9 @@ export function addColumnWidget(id: Column['id'], widget: ColumnWidget) {
 }
 
 export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
-	const columns = deepClone(store.state['deck.columns']);
-	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id);
-	const column = deepClone(store.state['deck.columns'][columnIndex]);
+	const columns = deepClone(store.s['deck.columns']);
+	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
+	const column = deepClone(store.s['deck.columns'][columnIndex]);
 	if (column == null) return;
 	if (column.widgets == null) column.widgets = [];
 	column.widgets = column.widgets.filter(w => w.id !== widget.id);
@@ -267,9 +267,9 @@ export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) {
 }
 
 export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
-	const columns = deepClone(store.state['deck.columns']);
-	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id);
-	const column = deepClone(store.state['deck.columns'][columnIndex]);
+	const columns = deepClone(store.s['deck.columns']);
+	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
+	const column = deepClone(store.s['deck.columns'][columnIndex]);
 	if (column == null) return;
 	column.widgets = widgets;
 	columns[columnIndex] = column;
@@ -278,9 +278,9 @@ export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) {
 }
 
 export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) {
-	const columns = deepClone(store.state['deck.columns']);
-	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id);
-	const column = deepClone(store.state['deck.columns'][columnIndex]);
+	const columns = deepClone(store.s['deck.columns']);
+	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
+	const column = deepClone(store.s['deck.columns'][columnIndex]);
 	if (column == null) return;
 	if (column.widgets == null) column.widgets = [];
 	column.widgets = column.widgets.map(w => w.id === widgetId ? {
@@ -293,9 +293,9 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat
 }
 
 export function updateColumn(id: Column['id'], column: Partial<Column>) {
-	const columns = deepClone(store.state['deck.columns']);
-	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id);
-	const currentColumn = deepClone(store.state['deck.columns'][columnIndex]);
+	const columns = deepClone(store.s['deck.columns']);
+	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id);
+	const currentColumn = deepClone(store.s['deck.columns'][columnIndex]);
 	if (currentColumn == null) return;
 	for (const [k, v] of Object.entries(column)) {
 		currentColumn[k] = v;
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index d734ea64b1..5316398ba4 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -406,7 +406,7 @@ const easterEggEngine = ref<{ stop: () => void } | null>(null);
 const containerEl = shallowRef<HTMLElement>();
 
 function iconLoaded() {
-	const emojis = store.state.reactions;
+	const emojis = store.s.reactions;
 	const containerWidth = containerEl.value.offsetWidth;
 	for (let i = 0; i < 32; i++) {
 		easterEggEmojis.value.push({
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 7df25f48c1..8b396c1821 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton>
 			</div>
 
-			<MkInfo v-if="!store.reactiveState.abusesTutorial.value" closable @close="closeTutorial()">
+			<MkInfo v-if="!store.r.abusesTutorial.value" closable @close="closeTutorial()">
 				{{ i18n.ts._abuseUserReport.resolveTutorial }}
 			</MkInfo>
 
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index bb7eef36ad..7cada9b5be 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -54,7 +54,7 @@ async function renderChart() {
 
 	const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const colorRead = '#3498db';
 	const colorWrite = '#2ecc71';
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index dd5d4b0e72..bbfe60d205 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -68,7 +68,7 @@ onMounted(async () => {
 
 	const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 	const succColor = '#87e000';
 	const failColor = '#ff4400';
 
diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue
index 6f461b838d..708768a1e4 100644
--- a/packages/frontend/src/pages/admin/overview.queue.chart.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue
@@ -67,7 +67,7 @@ const color =
 	'?' as never;
 
 onMounted(() => {
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	chartInstance = new Chart(chartEl.value, {
 		type: 'line',
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue
index 77d3332523..071d4b2f51 100644
--- a/packages/frontend/src/pages/admin/queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue
@@ -67,7 +67,7 @@ const color =
 	'?' as never;
 
 onMounted(() => {
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	chartInstance = new Chart(chartEl.value, {
 		type: 'line',
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index 3478a11ad4..6a48cde290 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 
 			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
-				<img v-if="store.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
+				<img v-if="store.s.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
 				<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
 				<canvas ref="canvasEl" :class="$style.canvas"/>
 				<Transition
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index c453952613..36cebb05dc 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -154,8 +154,8 @@ import MkFolder from '@/components/MkFolder.vue';
 import { prefer } from '@/preferences.js';
 import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue';
 
-const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.state.reactions));
-const pinnedEmojis: Ref<string[]> = ref(deepClone(store.state.pinnedEmojis));
+const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.s.reactions));
+const pinnedEmojis: Ref<string[]> = ref(deepClone(store.s.pinnedEmojis));
 
 const emojiPickerScale = prefer.model('emojiPickerScale');
 const emojiPickerWidth = prefer.model('emojiPickerWidth');
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index 43fb20bb3c..1048f42027 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -159,7 +159,7 @@ import { store } from '@/store.js';
 
 const excludeMutingUsers = ref(false);
 const excludeInactiveUsers = ref(false);
-const withReplies = ref(store.state.defaultWithReplies);
+const withReplies = ref(store.s.defaultWithReplies);
 
 const onExportSuccess = () => {
 	os.alert({
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 9a87f404e7..b782a575c7 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div v-if="!narrow || currentPage?.route.name == null" class="nav">
 					<div class="baaadecd">
 						<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo>
-						<MkInfo v-if="!store.reactiveState.enablePreferencesAutoCloudBackup.value && store.reactiveState.showPreferencesAutoCloudBackupSuggestion.value" class="info">
+						<MkInfo v-if="!store.r.enablePreferencesAutoCloudBackup.value && store.r.showPreferencesAutoCloudBackupSuggestion.value" class="info">
 							<div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div>
 							<div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div>
 						</MkInfo>
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue
index a8032ab266..bf762d8aeb 100644
--- a/packages/frontend/src/pages/settings/preferences.vue
+++ b/packages/frontend/src/pages/settings/preferences.vue
@@ -337,8 +337,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template>
 							<div class="_buttons">
 								<template v-for="lang in emojiIndexLangs" :key="lang">
-									<MkButton v-if="store.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
-									<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
+									<MkButton v-if="store.r.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
+									<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.r.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
 								</template>
 							</div>
 						</MkFolder>
@@ -449,7 +449,7 @@ function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
 
 function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
 	async function main() {
-		const currentIndexes = store.state.additionalUnicodeEmojiIndexes;
+		const currentIndexes = store.s.additionalUnicodeEmojiIndexes;
 
 		function download() {
 			switch (lang) {
@@ -469,7 +469,7 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
 
 function removeEmojiIndex(lang: string) {
 	async function main() {
-		const currentIndexes = store.state.additionalUnicodeEmojiIndexes;
+		const currentIndexes = store.s.additionalUnicodeEmojiIndexes;
 		delete currentIndexes[lang];
 		await store.set('additionalUnicodeEmojiIndexes', currentIndexes);
 	}
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 391ec01a1d..64e16987a9 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -200,7 +200,7 @@ async function saveAs() {
 	if (description.value) theme.value.desc = description.value;
 	await addTheme(theme.value);
 	applyTheme(theme.value);
-	if (store.state.darkMode) {
+	if (store.s.darkMode) {
 		prefer.set('darkTheme', theme.value);
 	} else {
 		prefer.set('lightTheme', theme.value);
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index ab7d22ec59..8d64b0a974 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkSpacer :contentMax="800">
 		<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
 			<div :key="src" ref="rootEl">
-				<MkInfo v-if="isBasicTimeline(src) && !store.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
+				<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()">
 					{{ i18n.ts._timelineDescription[src] }}
 				</MkInfo>
 				<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/>
@@ -67,18 +67,18 @@ type TimelinePageSrc = BasicTimelineType | `list:${string}`;
 const queue = ref(0);
 const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global');
 const src = computed<TimelinePageSrc>({
-	get: () => ($i ? store.reactiveState.tl.value.src : srcWhenNotSignin.value),
+	get: () => ($i ? store.r.tl.value.src : srcWhenNotSignin.value),
 	set: (x) => saveSrc(x),
 });
 const withRenotes = computed<boolean>({
-	get: () => store.reactiveState.tl.value.filter.withRenotes,
+	get: () => store.r.tl.value.filter.withRenotes,
 	set: (x) => saveTlFilter('withRenotes', x),
 });
 
 // computed内での無限ループを防ぐためのフラグ
 const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>(
-	store.reactiveState.tl.value.filter.withReplies ? 'withReplies' :
-	store.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' :
+	store.r.tl.value.filter.withReplies ? 'withReplies' :
+	store.r.tl.value.filter.onlyFiles ? 'onlyFiles' :
 	false,
 );
 
@@ -88,7 +88,7 @@ const withReplies = computed<boolean>({
 		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') {
 			return false;
 		} else {
-			return store.reactiveState.tl.value.filter.withReplies;
+			return store.r.tl.value.filter.withReplies;
 		}
 	},
 	set: (x) => saveTlFilter('withReplies', x),
@@ -98,7 +98,7 @@ const onlyFiles = computed<boolean>({
 		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') {
 			return false;
 		} else {
-			return store.reactiveState.tl.value.filter.onlyFiles;
+			return store.r.tl.value.filter.onlyFiles;
 		}
 	},
 	set: (x) => saveTlFilter('onlyFiles', x),
@@ -115,7 +115,7 @@ watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => {
 });
 
 const withSensitive = computed<boolean>({
-	get: () => store.reactiveState.tl.value.filter.withSensitive,
+	get: () => store.r.tl.value.filter.withSensitive,
 	set: (x) => saveTlFilter('withSensitive', x),
 });
 
@@ -196,7 +196,7 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
 }
 
 function saveSrc(newSrc: TimelinePageSrc): void {
-	const out = deepMerge({ src: newSrc }, store.state.tl);
+	const out = deepMerge({ src: newSrc }, store.s.tl);
 
 	if (newSrc.startsWith('userList:')) {
 		const id = newSrc.substring('userList:'.length);
@@ -209,9 +209,9 @@ function saveSrc(newSrc: TimelinePageSrc): void {
 	}
 }
 
-function saveTlFilter(key: keyof typeof store.state.tl.filter, newValue: boolean) {
+function saveTlFilter(key: keyof typeof store.s.tl.filter, newValue: boolean) {
 	if (key !== 'withReplies' || $i) {
-		const out = deepMerge({ filter: { [key]: newValue } }, store.state.tl);
+		const out = deepMerge({ filter: { [key]: newValue } }, store.s.tl);
 		store.set('tl', out);
 	}
 }
@@ -231,7 +231,7 @@ function focus(): void {
 
 function closeTutorial(): void {
 	if (!isBasicTimeline(src.value)) return;
-	const before = store.state.timelineTutorials;
+	const before = store.s.timelineTutorials;
 	before[src.value] = true;
 	store.set('timelineTutorials', before);
 }
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index b4c0fe4052..a86387571a 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -64,7 +64,7 @@ async function renderChart() {
 
 	const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' });
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const colorFollowLocal = '#008FFB';
 	const colorFollowRemote = '#008FFB88';
diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index 56c5204817..d083fdebed 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -64,7 +64,7 @@ async function renderChart() {
 
 	const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const colorNormal = '#008FFB';
 	const colorReply = '#FEB019';
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 1b81fda95f..d5e8f45608 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -64,7 +64,7 @@ async function renderChart() {
 
 	const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' });
 
-	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
+	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const colorUser = '#3498db';
 	const colorVisitor = '#2ecc71';
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 871e454ff0..f55b1e93cf 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -45,8 +45,15 @@ export class Storage<T extends StateDef> {
 	public readonly def: T;
 
 	// TODO: これが実装されたらreadonlyにしたい: https://github.com/microsoft/TypeScript/issues/37487
-	public readonly state: State<T>;
-	public readonly reactiveState: ReactiveState<T>;
+	/**
+	 * static / state の略 (static が予約語のため)
+	 */
+	public readonly s: State<T>;
+
+	/**
+	 * reactive の略
+	 */
+	public readonly r: ReactiveState<T>;
 
 	private pizzaxChannel: BroadcastChannel<PizzaxChannelMessage<T>>;
 
@@ -70,12 +77,12 @@ export class Storage<T extends StateDef> {
 
 		this.pizzaxChannel = new BroadcastChannel(`pizzax::${key}`);
 
-		this.state = {} as State<T>;
-		this.reactiveState = {} as ReactiveState<T>;
+		this.s = {} as State<T>;
+		this.r = {} as ReactiveState<T>;
 
 		for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) {
-			this.state[k] = v.default;
-			this.reactiveState[k] = ref(v.default);
+			this.s[k] = v.default;
+			this.r[k] = ref(v.default);
 		}
 
 		this.ready = this.init();
@@ -106,13 +113,13 @@ export class Storage<T extends StateDef> {
 
 		for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) {
 			if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) {
-				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceState[k], v.default);
+				this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(deviceState[k], v.default);
 			} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) {
-				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(registryCache[k], v.default);
+				this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(registryCache[k], v.default);
 			} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
-				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
+				this.r[k].value = this.s[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
 			} else {
-				this.reactiveState[k].value = this.state[k] = v.default;
+				this.r[k].value = this.s[k] = v.default;
 			}
 		}
 
@@ -120,7 +127,7 @@ export class Storage<T extends StateDef> {
 			// アカウント変更すればunisonReloadが効くため、このreturnが発火することは
 			// まずないと思うけど一応弾いておく
 			if (where === 'deviceAccount' && !($i && userId !== $i.id)) return;
-			this.reactiveState[key].value = this.state[key] = value;
+			this.r[key].value = this.s[key] = value;
 		});
 
 		if ($i) {
@@ -128,9 +135,9 @@ export class Storage<T extends StateDef> {
 
 			// streamingのuser storage updateイベントを監視して更新
 			connection.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => {
-				if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return;
+				if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.s[key] === value) return;
 
-				this.reactiveState[key].value = this.state[key] = value;
+				this.r[key].value = this.s[key] = value;
 
 				this.addIdbSetJob(async () => {
 					const cache = await get(this.registryCacheKeyName);
@@ -156,10 +163,10 @@ export class Storage<T extends StateDef> {
 							for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) {
 								if (v.where === 'account') {
 									if (Object.prototype.hasOwnProperty.call(kvs, k)) {
-										this.reactiveState[k].value = this.state[k] = (kvs as Partial<T>)[k];
+										this.r[k].value = this.s[k] = (kvs as Partial<T>)[k];
 										cache[k] = (kvs as Partial<T>)[k];
 									} else {
-										this.reactiveState[k].value = this.state[k] = v.default;
+										this.r[k].value = this.s[k] = v.default;
 									}
 								}
 							}
@@ -179,7 +186,7 @@ export class Storage<T extends StateDef> {
 		// (JSON.parse(JSON.stringify(value))の代わり)
 		const rawValue = deepClone(value);
 
-		this.reactiveState[key].value = this.state[key] = rawValue;
+		this.r[key].value = this.s[key] = rawValue;
 
 		return this.addIdbSetJob(async () => {
 			switch (this.def[key].where) {
@@ -224,7 +231,7 @@ export class Storage<T extends StateDef> {
 	}
 
 	public push<K extends keyof T>(key: K, value: ArrayElement<T[K]['default']>): void {
-		const currentState = this.state[key];
+		const currentState = this.s[key];
 		this.set(key, [...currentState, value]);
 	}
 
@@ -246,9 +253,9 @@ export class Storage<T extends StateDef> {
 			get: () => R;
 			set: (value: R) => void;
 		} {
-		const valueRef = ref(this.state[key]);
+		const valueRef = ref(this.s[key]);
 
-		const stop = watch(this.reactiveState[key], val => {
+		const stop = watch(this.r[key], val => {
 			valueRef.value = val;
 		});
 
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 5e33d196c3..25d69cd727 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -93,7 +93,7 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
 
 export async function authorizePlugin(plugin: Plugin) {
 	if (plugin.permissions == null || plugin.permissions.length === 0) return;
-	if (Object.hasOwn(store.state.pluginTokens, plugin.installId)) return;
+	if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) return;
 
 	const token = await new Promise<string>((res, rej) => {
 		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {
@@ -116,7 +116,7 @@ export async function authorizePlugin(plugin: Plugin) {
 	});
 
 	store.set('pluginTokens', {
-		...store.state.pluginTokens,
+		...store.s.pluginTokens,
 		[plugin.installId]: token,
 	});
 }
@@ -155,11 +155,11 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
 export async function uninstallPlugin(plugin: Plugin) {
 	abortPlugin(plugin);
 	prefer.set('plugins', prefer.s.plugins.filter(x => x.installId !== plugin.installId));
-	if (Object.hasOwn(store.state.pluginTokens, plugin.installId)) {
+	if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) {
 		await os.apiWithDialog('i/revoke-token', {
-			token: store.state.pluginTokens[plugin.installId],
+			token: store.s.pluginTokens[plugin.installId],
 		});
-		const pluginTokens = { ...store.state.pluginTokens };
+		const pluginTokens = { ...store.s.pluginTokens };
 		delete pluginTokens[plugin.installId];
 		store.set('pluginTokens', pluginTokens);
 	}
@@ -341,7 +341,7 @@ function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record<s
 	}
 
 	const env: Record<string, values.Value> = {
-		...createAiScriptEnv({ ...opts, token: store.state.pluginTokens[id] }),
+		...createAiScriptEnv({ ...opts, token: store.s.pluginTokens[id] }),
 
 		'Plugin:register:post_form_action': values.FN_NATIVE(([title, handler]) => {
 			utils.assertString(title);
diff --git a/packages/frontend/src/preferences.ts b/packages/frontend/src/preferences.ts
index 03da3be465..a38f1a2a33 100644
--- a/packages/frontend/src/preferences.ts
+++ b/packages/frontend/src/preferences.ts
@@ -65,7 +65,7 @@ let latestBackupAt = 0;
 
 window.setInterval(() => {
 	if ($i == null) return;
-	if (!store.state.enablePreferencesAutoCloudBackup) return;
+	if (!store.s.enablePreferencesAutoCloudBackup) return;
 	if (document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ
 	if (profileManager.profile.modifiedAt <= latestBackupAt) return;
 
diff --git a/packages/frontend/src/preferences/store.ts b/packages/frontend/src/preferences/store.ts
index 1029346d81..58684ce024 100644
--- a/packages/frontend/src/preferences/store.ts
+++ b/packages/frontend/src/preferences/store.ts
@@ -22,7 +22,7 @@ type StoreEvent<Data extends Record<string, any>> = {
 
 export class Store<Data extends Record<string, any>> extends EventEmitter<StoreEvent<Data>> {
 	/**
-	 * static の略 (static が予約語のため)
+	 * static / state の略 (static が予約語のため)
 	 */
 	public s = {} as {
 		[K in keyof Data]: Data[K];
diff --git a/packages/frontend/src/preferences/utility.ts b/packages/frontend/src/preferences/utility.ts
index 11bebc6ab2..64b2bde4de 100644
--- a/packages/frontend/src/preferences/utility.ts
+++ b/packages/frontend/src/preferences/utility.ts
@@ -21,7 +21,7 @@ function canAutoBackup() {
 }
 
 export function getPreferencesProfileMenu(): MenuItem[] {
-	const autoBackupEnabled = ref(store.state.enablePreferencesAutoCloudBackup);
+	const autoBackupEnabled = ref(store.s.enablePreferencesAutoCloudBackup);
 
 	watch(autoBackupEnabled, () => {
 		if (autoBackupEnabled.value) {
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 1797007f4a..bd5e0bba8a 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -105,7 +105,7 @@ const router = useRouter();
 
 const forceIconOnly = ref(window.innerWidth <= 1279);
 const iconOnly = computed(() => {
-	return forceIconOnly.value || (store.reactiveState.menuDisplay.value === 'sideIcon');
+	return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon');
 });
 
 const menu = computed(() => prefer.s.menu);
@@ -123,7 +123,7 @@ function calcViewState() {
 
 window.addEventListener('resize', calcViewState);
 
-watch(store.reactiveState.menuDisplay, () => {
+watch(store.r.menuDisplay, () => {
 	calcViewState();
 });
 
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index 747fa2ab85..259aad7401 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -100,7 +100,7 @@ function openAccountMenu(ev: MouseEvent) {
 	}, ev);
 }
 
-watch(store.reactiveState.menuDisplay, () => {
+watch(store.r.menuDisplay, () => {
 	calcViewState();
 });
 
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 12791dba30..3d4e8ed19d 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -75,7 +75,7 @@ const widgetsShowing = ref(false);
 const fullView = ref(false);
 const globalHeaderHeight = ref(0);
 const wallpaper = miLocalStorage.getItem('wallpaper') != null;
-const showMenuOnTop = computed(() => store.state.menuDisplay === 'top');
+const showMenuOnTop = computed(() => store.s.menuDisplay === 'top');
 const live2d = shallowRef<HTMLIFrameElement>();
 const widgetsLeft = ref<HTMLElement>();
 const widgetsRight = ref<HTMLElement>();
@@ -97,7 +97,7 @@ provide('shouldHeaderThin', showMenuOnTop.value);
 provide('forceSpacerMin', true);
 
 function attachSticky(el: HTMLElement) {
-	const sticky = new StickySidebar(el, 0, store.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
+	const sticky = new StickySidebar(el, 0, store.s.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
 	window.addEventListener('scroll', () => {
 		sticky.calc(window.scrollY);
 	}, { passive: true });
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index b723f92ad6..08f8ca4f65 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<div :class="$style.sideMenu">
 				<div :class="$style.sideMenuTop">
-					<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${store.state['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
+					<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${store.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button>
 					<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button>
 				</div>
 				<div :class="$style.sideMenuMiddle">
@@ -137,7 +137,7 @@ const columnComponents = {
 
 mainRouter.navHook = (path, flag): boolean => {
 	if (flag === 'forcePage') return false;
-	const noMainColumn = !store.state['deck.columns'].some(x => x.type === 'main');
+	const noMainColumn = !store.s['deck.columns'].some(x => x.type === 'main');
 	if (prefer.s['deck.navWindow'] || noMainColumn) {
 		os.pageWindow(path);
 		return true;
@@ -160,8 +160,8 @@ watch(route, () => {
 });
 */
 
-const columns = store.reactiveState['deck.columns'];
-const layout = store.reactiveState['deck.layout'];
+const columns = store.r['deck.columns'];
+const layout = store.r['deck.layout'];
 const menuIndicated = computed(() => {
 	if ($i == null) return false;
 	for (const def in navbarItemDef) {
@@ -214,12 +214,12 @@ loadDeck();
 
 function changeProfile(ev: MouseEvent) {
 	let items: MenuItem[] = [{
-		text: store.state['deck.profile'],
+		text: store.s['deck.profile'],
 		active: true,
 		action: () => {},
 	}];
 	getProfiles().then(profiles => {
-		items.push(...(profiles.filter(k => k !== store.state['deck.profile']).map(k => ({
+		items.push(...(profiles.filter(k => k !== store.s['deck.profile']).map(k => ({
 			text: k,
 			action: () => {
 				store.set('deck.profile', k);
@@ -252,17 +252,17 @@ function changeProfile(ev: MouseEvent) {
 async function deleteProfile() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.tsx.deleteAreYouSure({ x: store.state['deck.profile'] }),
+		text: i18n.tsx.deleteAreYouSure({ x: store.s['deck.profile'] }),
 	});
 	if (canceled) return;
 
 	os.promiseDialog((async () => {
-		if (store.state['deck.profile'] === 'default') {
+		if (store.s['deck.profile'] === 'default') {
 			await store.set('deck.columns', []);
 			await store.set('deck.layout', []);
 			await forceSaveDeck();
 		} else {
-			await deleteProfile_(store.state['deck.profile']);
+			await deleteProfile_(store.s['deck.profile']);
 		}
 		await store.set('deck.profile', 'default');
 	})(), () => {
diff --git a/packages/frontend/src/utility/emoji-picker.ts b/packages/frontend/src/utility/emoji-picker.ts
index 7ff5863243..e7275b86f2 100644
--- a/packages/frontend/src/utility/emoji-picker.ts
+++ b/packages/frontend/src/utility/emoji-picker.ts
@@ -25,7 +25,7 @@ class EmojiPicker {
 	}
 
 	public async init() {
-		const emojisRef = store.reactiveState.pinnedEmojis;
+		const emojisRef = store.r.pinnedEmojis;
 		await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
 			src: this.src,
 			pinnedEmojis: emojisRef,
diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts
index 9c37070784..74eb326c4c 100644
--- a/packages/frontend/src/utility/get-note-menu.ts
+++ b/packages/frontend/src/utility/get-note-menu.ts
@@ -608,8 +608,8 @@ export function getRenoteMenu(props: {
 					});
 				}
 
-				const configuredVisibility = prefer.s.rememberNoteVisibility ? store.state.visibility : prefer.s.defaultNoteVisibility;
-				const localOnly = prefer.s.rememberNoteVisibility ? store.state.localOnly : prefer.s.defaultNoteLocalOnly;
+				const configuredVisibility = prefer.s.rememberNoteVisibility ? store.s.visibility : prefer.s.defaultNoteVisibility;
+				const localOnly = prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly;
 
 				let visibility = appearNote.visibility;
 				visibility = smallerVisibility(visibility, configuredVisibility);
diff --git a/packages/frontend/src/utility/init-chart.ts b/packages/frontend/src/utility/init-chart.ts
index 037b0d9567..9775b9fec4 100644
--- a/packages/frontend/src/utility/init-chart.ts
+++ b/packages/frontend/src/utility/init-chart.ts
@@ -52,7 +52,7 @@ export function initChart() {
 	// フォントカラー
 	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg');
 
-	Chart.defaults.borderColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
+	Chart.defaults.borderColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
 
 	Chart.defaults.animation = false;
 }
diff --git a/packages/frontend/src/utility/reaction-picker.ts b/packages/frontend/src/utility/reaction-picker.ts
index 81f6c02dcf..200fb0b686 100644
--- a/packages/frontend/src/utility/reaction-picker.ts
+++ b/packages/frontend/src/utility/reaction-picker.ts
@@ -21,7 +21,7 @@ class ReactionPicker {
 	}
 
 	public async init() {
-		const reactionsRef = store.reactiveState.reactions;
+		const reactionsRef = store.r.reactions;
 		await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
 			src: this.src,
 			pinnedEmojis: reactionsRef,
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index 256cbc8021..3df5c5bfd7 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -48,7 +48,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const text = ref<string | null>(store.state.memo);
+const text = ref<string | null>(store.s.memo);
 const changed = ref(false);
 let timeoutId;
 
@@ -63,7 +63,7 @@ const onChange = () => {
 	timeoutId = window.setTimeout(saveMemo, 1000);
 };
 
-watch(() => store.reactiveState.memo, newText => {
+watch(() => store.r.memo, newText => {
 	text.value = newText.value;
 });
 
-- 
cgit v1.2.3-freya


From 7b323031b774745ee2146c89ead2a9ebe628d613 Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Wed, 19 Mar 2025 18:46:03 +0900
Subject: refactor(frontend): use useTemplateRef for DOM referencing

---
 .../src/components/MkAbuseReportWindow.vue         |  4 ++--
 packages/frontend/src/components/MkAnimBg.vue      |  4 ++--
 .../src/components/MkAnnouncementDialog.vue        |  6 +++---
 .../src/components/MkAntennaEditorDialog.vue       |  4 ++--
 .../frontend/src/components/MkAutocomplete.vue     |  4 ++--
 packages/frontend/src/components/MkButton.vue      |  6 +++---
 packages/frontend/src/components/MkCaptcha.vue     |  4 ++--
 packages/frontend/src/components/MkChart.vue       | 14 +++++--------
 packages/frontend/src/components/MkCodeEditor.vue  |  4 ++--
 packages/frontend/src/components/MkColorInput.vue  |  4 ++--
 packages/frontend/src/components/MkContainer.vue   |  8 ++++----
 packages/frontend/src/components/MkContextMenu.vue |  4 ++--
 .../frontend/src/components/MkCropperDialog.vue    |  6 +++---
 .../src/components/MkCustomEmojiDetailedDialog.vue |  6 +++---
 packages/frontend/src/components/MkDialog.vue      |  4 ++--
 packages/frontend/src/components/MkDrive.vue       |  6 +++---
 .../src/components/MkDriveSelectDialog.vue         |  4 ++--
 .../src/components/MkEmbedCodeGenDialog.vue        |  8 ++++----
 packages/frontend/src/components/MkEmojiPicker.vue |  6 +++---
 .../src/components/MkEmojiPickerDialog.vue         |  6 +++---
 .../src/components/MkFileCaptionEditWindow.vue     |  4 ++--
 .../frontend/src/components/MkFoldableSection.vue  |  4 ++--
 packages/frontend/src/components/MkFolder.vue      |  4 ++--
 packages/frontend/src/components/MkFormDialog.vue  |  4 ++--
 packages/frontend/src/components/MkHeatmap.vue     |  6 +++---
 .../frontend/src/components/MkHorizontalSwipe.vue  |  4 ++--
 .../frontend/src/components/MkImgWithBlurhash.vue  |  8 ++++----
 packages/frontend/src/components/MkInput.vue       |  8 ++++----
 .../frontend/src/components/MkInstanceStats.vue    |  8 ++++----
 packages/frontend/src/components/MkLaunchPad.vue   |  4 ++--
 packages/frontend/src/components/MkMediaAudio.vue  |  6 +++---
 packages/frontend/src/components/MkMediaList.vue   |  4 ++--
 packages/frontend/src/components/MkMediaVideo.vue  |  6 +++---
 packages/frontend/src/components/MkMenu.child.vue  |  4 ++--
 packages/frontend/src/components/MkMenu.vue        |  8 ++++----
 packages/frontend/src/components/MkModal.vue       |  6 +++---
 packages/frontend/src/components/MkModalWindow.vue |  8 ++++----
 packages/frontend/src/components/MkNote.vue        | 16 +++++++--------
 .../frontend/src/components/MkNoteDetailed.vue     | 16 +++++++--------
 packages/frontend/src/components/MkNotes.vue       |  4 ++--
 .../src/components/MkNotificationSelectWindow.vue  |  8 ++++----
 .../frontend/src/components/MkNotifications.vue    |  4 ++--
 packages/frontend/src/components/MkOmit.vue        |  4 ++--
 packages/frontend/src/components/MkPageWindow.vue  |  4 ++--
 packages/frontend/src/components/MkPagination.vue  |  4 ++--
 .../frontend/src/components/MkPasswordDialog.vue   |  6 +++---
 packages/frontend/src/components/MkPopupMenu.vue   |  4 ++--
 packages/frontend/src/components/MkPostForm.vue    | 12 +++++------
 .../frontend/src/components/MkPostFormDialog.vue   |  7 +++----
 .../frontend/src/components/MkPullToRefresh.vue    |  6 +++---
 packages/frontend/src/components/MkRange.vue       |  6 +++---
 .../frontend/src/components/MkReactionIcon.vue     |  4 ++--
 .../src/components/MkReactionsViewer.reaction.vue  |  4 ++--
 .../frontend/src/components/MkRetentionHeatmap.vue |  6 +++---
 .../src/components/MkRetentionLineChart.vue        |  4 ++--
 .../frontend/src/components/MkSigninDialog.vue     |  4 ++--
 .../frontend/src/components/MkSignupDialog.vue     |  4 ++--
 packages/frontend/src/components/MkSparkle.vue     |  4 ++--
 .../src/components/MkSystemWebhookEditor.vue       |  8 ++++----
 packages/frontend/src/components/MkTagCloud.vue    |  8 ++++----
 packages/frontend/src/components/MkTextarea.vue    |  4 ++--
 packages/frontend/src/components/MkTimeline.vue    |  6 +++---
 .../src/components/MkTokenGenerateWindow.vue       |  4 ++--
 packages/frontend/src/components/MkTooltip.vue     |  4 ++--
 .../frontend/src/components/MkTutorialDialog.vue   |  6 +++---
 packages/frontend/src/components/MkUpdated.vue     |  6 +++---
 .../frontend/src/components/MkUserSelectDialog.vue |  4 ++--
 .../frontend/src/components/MkUserSetupDialog.vue  |  9 ++++----
 .../frontend/src/components/MkVisibilityPicker.vue |  4 ++--
 .../MkVisitorDashboard.ActiveUsersChart.vue        |  4 ++--
 .../frontend/src/components/MkWaitingDialog.vue    |  4 ++--
 packages/frontend/src/components/MkWindow.vue      |  4 ++--
 packages/frontend/src/components/global/MkA.vue    |  4 ++--
 packages/frontend/src/components/global/MkLazy.vue |  4 ++--
 .../src/components/global/MkPageHeader.tabs.vue    |  6 +++---
 .../src/components/global/MkPageHeader.vue         |  4 ++--
 .../src/components/global/MkStickyContainer.vue    |  3 +--
 .../frontend/src/components/grid/MkDataCell.vue    | 24 ++++++++++++----------
 packages/frontend/src/pages/about-misskey.vue      |  4 ++--
 packages/frontend/src/pages/admin/_header_.vue     |  6 +++---
 .../abuse-report/notification-recipient.editor.vue |  6 +++---
 packages/frontend/src/pages/admin/abuses.vue       |  4 ++--
 packages/frontend/src/pages/admin/invites.vue      |  6 +++---
 packages/frontend/src/pages/admin/modlog.vue       |  4 ++--
 .../src/pages/admin/overview.active-users.vue      |  4 ++--
 .../src/pages/admin/overview.ap-requests.vue       |  6 +++---
 packages/frontend/src/pages/admin/overview.pie.vue |  4 ++--
 .../src/pages/admin/overview.queue.chart.vue       |  4 ++--
 .../frontend/src/pages/admin/overview.queue.vue    | 10 ++++-----
 packages/frontend/src/pages/admin/overview.vue     |  4 ++--
 .../frontend/src/pages/admin/queue.chart.chart.vue |  4 ++--
 packages/frontend/src/pages/admin/queue.chart.vue  | 10 ++++-----
 packages/frontend/src/pages/admin/users.vue        |  4 ++--
 packages/frontend/src/pages/antenna-timeline.vue   |  8 ++++----
 .../frontend/src/pages/custom-emojis-manager.vue   |  4 ++--
 .../frontend/src/pages/drop-and-fusion.game.vue    |  6 +++---
 packages/frontend/src/pages/explore.users.vue      |  4 ++--
 packages/frontend/src/pages/explore.vue            |  4 ++--
 packages/frontend/src/pages/follow-requests.vue    |  4 ++--
 packages/frontend/src/pages/invite.vue             |  4 ++--
 packages/frontend/src/pages/my-clips/index.vue     |  4 ++--
 .../pages/page-editor/els/page-editor.el.text.vue  |  5 ++---
 .../frontend/src/pages/settings/2fa.qrdialog.vue   |  4 ++--
 .../pages/settings/avatar-decoration.dialog.vue    |  4 ++--
 packages/frontend/src/pages/settings/index.vue     |  4 ++--
 .../frontend/src/pages/settings/notifications.vue  |  4 ++--
 packages/frontend/src/pages/timeline.vue           |  6 +++---
 packages/frontend/src/pages/user-list-timeline.vue |  8 ++++----
 .../frontend/src/pages/user/activity.following.vue |  8 ++++----
 .../frontend/src/pages/user/activity.notes.vue     |  8 ++++----
 packages/frontend/src/pages/user/activity.pv.vue   |  8 ++++----
 .../frontend/src/pages/welcome.timeline.note.vue   |  4 ++--
 packages/frontend/src/pages/welcome.timeline.vue   |  6 +++---
 packages/frontend/src/ui/classic.sidebar.vue       |  4 ++--
 packages/frontend/src/ui/classic.vue               |  4 ++--
 packages/frontend/src/ui/deck.vue                  |  4 ++--
 packages/frontend/src/ui/deck/antenna-column.vue   |  4 ++--
 packages/frontend/src/ui/deck/channel-column.vue   |  4 ++--
 packages/frontend/src/ui/deck/column.vue           |  4 ++--
 packages/frontend/src/ui/deck/list-column.vue      |  4 ++--
 .../frontend/src/ui/deck/notifications-column.vue  |  6 +++---
 .../frontend/src/ui/deck/role-timeline-column.vue  |  4 ++--
 packages/frontend/src/ui/deck/tl-column.vue        |  4 ++--
 packages/frontend/src/ui/universal.vue             |  4 ++--
 packages/frontend/src/widgets/WidgetAichan.vue     |  4 ++--
 .../frontend/src/widgets/WidgetInstanceCloud.vue   |  8 ++++----
 packages/frontend/src/widgets/WidgetSlideshow.vue  |  8 ++++----
 127 files changed, 353 insertions(+), 359 deletions(-)

(limited to 'packages/frontend/src/components/MkUserSetupDialog.vue')

diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue
index a634a748e9..dbac5e9dd7 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.vue
+++ b/packages/frontend/src/components/MkAbuseReportWindow.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { ref, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkWindow from '@/components/MkWindow.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -47,7 +47,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
+const uiWindow = useTemplateRef('uiWindow');
 const comment = ref(props.initialComment ?? '');
 
 function send() {
diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue
index 4bf6125af5..2938645557 100644
--- a/packages/frontend/src/components/MkAnimBg.vue
+++ b/packages/frontend/src/components/MkAnimBg.vue
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef } from 'vue';
 import isChromatic from 'chromatic/isChromatic';
 
-const canvasEl = shallowRef<HTMLCanvasElement>();
+const canvasEl = useTemplateRef('canvasEl');
 
 const props = withDefaults(defineProps<{
 	scale?: number;
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue
index 582bb137bc..6e5b29654b 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.vue
+++ b/packages/frontend/src/components/MkAnnouncementDialog.vue
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
@@ -37,8 +37,8 @@ const props = withDefaults(defineProps<{
 }>(), {
 });
 
-const rootEl = shallowRef<HTMLDivElement>();
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const rootEl = useTemplateRef('rootEl');
+const modal = useTemplateRef('modal');
 
 async function ok() {
 	if (props.announcement.needConfirmationToRead) {
diff --git a/packages/frontend/src/components/MkAntennaEditorDialog.vue b/packages/frontend/src/components/MkAntennaEditorDialog.vue
index 6d815d29f3..0ebf5abf4c 100644
--- a/packages/frontend/src/components/MkAntennaEditorDialog.vue
+++ b/packages/frontend/src/components/MkAntennaEditorDialog.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import XAntennaEditor from '@/components/MkAntennaEditor.vue';
@@ -40,7 +40,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void,
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 
 function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
 	emit('created', newAntenna);
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 7436d4f1b2..5b747a2a1a 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
-import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
+import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
 import sanitizeHtml from 'sanitize-html';
 import { emojilist, getEmojiName } from '@@/js/emojilist.js';
 import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
@@ -139,7 +139,7 @@ const emit = defineEmits<{
 }>();
 
 const suggests = ref<Element>();
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
 
 const fetching = ref(true);
 const users = ref<any[]>([]);
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index 5b32ec0fc8..c81edc2a73 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, shallowRef } from 'vue';
+import { nextTick, onMounted, useTemplateRef } from 'vue';
 
 const props = defineProps<{
 	type?: 'button' | 'submit' | 'reset';
@@ -64,8 +64,8 @@ const emit = defineEmits<{
 	(ev: 'click', payload: MouseEvent): void;
 }>();
 
-const el = shallowRef<HTMLElement | null>(null);
-const ripples = shallowRef<HTMLElement | null>(null);
+const el = useTemplateRef('el');
+const ripples = useTemplateRef('ripples');
 
 onMounted(() => {
 	if (props.autofocus) {
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index 30eafb7a5b..e4f953bda8 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
+import { ref, useTemplateRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
 import { store } from '@/store.js';
 
 // APIs provided by Captcha services
@@ -69,7 +69,7 @@ const emit = defineEmits<{
 
 const available = ref(false);
 
-const captchaEl = shallowRef<HTMLDivElement | undefined>();
+const captchaEl = useTemplateRef('captchaEl');
 const captchaWidgetId = ref<string | undefined>(undefined);
 const testcaptchaInput = ref('');
 const testcaptchaPassed = ref(false);
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index d9f4558014..7e164362c1 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -45,12 +45,8 @@ export type ChartSrc =
 </script>
 
 <script lang="ts" setup>
-/* eslint-disable id-denylist --
-  Chart.js has a `data` attribute in most chart definitions, which triggers the
-  id-denylist violation when setting it. This is causing about 60+ lint issues.
-  As this is part of Chart.js's API it makes sense to disable the check here.
-*/
-import { onMounted, ref, shallowRef, watch } from 'vue';
+
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
 import { Chart } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import { misskeyApiGet } from '@/utility/misskey-api.js';
@@ -96,7 +92,7 @@ const props = withDefaults(defineProps<{
 	nowForChromatic: undefined,
 });
 
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const legendEl = useTemplateRef('legendEl');
 
 const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
 const negate = arr => arr.map(x => -x);
@@ -134,7 +130,7 @@ let chartData: {
 	bytes?: boolean;
 } | null = null;
 
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const chartEl = useTemplateRef('chartEl');
 const fetching = ref(true);
 
 const getDate = (ago: number) => {
@@ -849,7 +845,7 @@ watch(() => [props.src, props.span], fetchAndRender);
 onMounted(() => {
 	fetchAndRender();
 });
-/* eslint-enable id-denylist */
+
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue
index 5bf2301e72..46fdf15b5d 100644
--- a/packages/frontend/src/components/MkCodeEditor.vue
+++ b/packages/frontend/src/components/MkCodeEditor.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, watch, toRefs, shallowRef, nextTick } from 'vue';
+import { ref, watch, toRefs, useTemplateRef, nextTick } from 'vue';
 import { debounce } from 'throttle-debounce';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
@@ -61,7 +61,7 @@ const { modelValue } = toRefs(props);
 const v = ref<string>(modelValue.value ?? '');
 const focused = ref(false);
 const changed = ref(false);
-const inputEl = shallowRef<HTMLTextAreaElement>();
+const inputEl = useTemplateRef('inputEl');
 
 const focus = () => inputEl.value?.focus();
 
diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue
index 55a32664de..50931cc318 100644
--- a/packages/frontend/src/components/MkColorInput.vue
+++ b/packages/frontend/src/components/MkColorInput.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, toRefs } from 'vue';
+import { ref, useTemplateRef, toRefs } from 'vue';
 
 const props = defineProps<{
 	modelValue: string | null;
@@ -39,7 +39,7 @@ const emit = defineEmits<{
 
 const { modelValue } = toRefs(props);
 const v = ref(modelValue.value);
-const inputEl = shallowRef<HTMLElement>();
+const inputEl = useTemplateRef('inputEl');
 
 const onInput = () => {
 	emit('update:modelValue', v.value ?? '');
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index 169f3df7a4..39ca39aad7 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
 import { prefer } from '@/preferences.js';
 import { i18n } from '@/i18n.js';
 
@@ -58,9 +58,9 @@ const props = withDefaults(defineProps<{
 	maxHeight: null,
 });
 
-const rootEl = shallowRef<HTMLElement>();
-const contentEl = shallowRef<HTMLElement>();
-const headerEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const contentEl = useTemplateRef('contentEl');
+const headerEl = useTemplateRef('headerEl');
 const showBody = ref(props.expanded);
 const ignoreOmit = ref(false);
 const omitted = ref(false);
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 46da0840fa..e47dba4bae 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, shallowRef, ref } from 'vue';
+import { onMounted, onBeforeUnmount, useTemplateRef, ref } from 'vue';
 import MkMenu from './MkMenu.vue';
 import type { MenuItem } from '@/types/menu.js';
 import contains from '@/utility/contains.js';
@@ -34,7 +34,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
 
 const zIndex = ref<number>(os.claimZIndex('high'));
 
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 4a89fb30ca..bd2e6a9cbc 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, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import Cropper from 'cropperjs';
 import tinycolor from 'tinycolor2';
@@ -56,8 +56,8 @@ const props = defineProps<{
 }>();
 
 const imgUrl = getProxiedImageUrl(props.file.url, undefined, true);
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
-const imgEl = shallowRef<HTMLImageElement>();
+const dialogEl = useTemplateRef('dialogEl');
+const imgEl = useTemplateRef('imgEl');
 let cropper: Cropper | null = null;
 const loading = ref(true);
 
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
index 86d6269c69..54fda6bf7c 100644
--- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -57,14 +57,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import MkLink from '@/components/MkLink.vue';
 import { i18n } from '@/i18n.js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 
 const props = defineProps<{
-  emoji: Misskey.entities.EmojiDetailed,
+	emoji: Misskey.entities.EmojiDetailed,
 }>();
 
 const emit = defineEmits<{
@@ -73,7 +73,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
 
 function cancel() {
 	emit('cancel');
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 312c7ab58f..81d508c161 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, computed } from 'vue';
+import { ref, useTemplateRef, computed } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -117,7 +117,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
 
 const inputValue = ref<string | number | null>(props.input?.default ?? null);
 const selectedValue = ref(props.select?.default ?? null);
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index 270ef51916..edbdf1cca1 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
+import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from './MkButton.vue';
 import type { MenuItem } from '@/types/menu.js';
@@ -129,8 +129,8 @@ const emit = defineEmits<{
 	(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
 }>();
 
-const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
-const fileInput = shallowRef<HTMLInputElement>();
+const loadMoreFiles = useTemplateRef('loadMoreFiles');
+const fileInput = useTemplateRef('fileInput');
 
 const folder = ref<Misskey.entities.DriveFolder | null>(null);
 const files = ref<Misskey.entities.DriveFile[]>([]);
diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue
index f1ecc27123..1b9455e3f3 100644
--- a/packages/frontend/src/components/MkDriveSelectDialog.vue
+++ b/packages/frontend/src/components/MkDriveSelectDialog.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XDrive from '@/components/MkDrive.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -43,7 +43,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 
 const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);
 
diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
index e038b84ba7..a4e9547f90 100644
--- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
+++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue
@@ -89,7 +89,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { shallowRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
+import { useTemplateRef, ref, computed, nextTick, onMounted, onDeactivated, onUnmounted } from 'vue';
 import { url } from '@@/js/config.js';
 import { embedRouteWithScrollbar } from '@@/js/embed-page.js';
 import type { EmbeddableEntity, EmbedParams } from '@@/js/embed-page.js';
@@ -121,7 +121,7 @@ const props = defineProps<{
 }>();
 
 //#region Modalの制御
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
 
 function cancel() {
 	emit('cancel');
@@ -198,9 +198,9 @@ function doCopy() {
 //#endregion
 
 //#region プレビューのリサイズ
-const resizerRootEl = shallowRef<HTMLDivElement>();
+const resizerRootEl = useTemplateRef('resizerRootEl');
 const iframeLoading = ref(true);
-const iframeEl = shallowRef<HTMLIFrameElement>();
+const iframeEl = useTemplateRef('iframeEl');
 const iframeHeight = ref(0);
 const iframeScale = ref(1);
 const iframeStyle = computed(() => {
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index c0883b1342..d4367f6ee8 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -115,7 +115,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, computed, watch, onMounted } from 'vue';
+import { ref, useTemplateRef, computed, watch, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import {
 	emojilist,
@@ -157,8 +157,8 @@ const emit = defineEmits<{
 	(ev: 'esc'): void;
 }>();
 
-const searchEl = shallowRef<HTMLInputElement>();
-const emojisEl = shallowRef<HTMLDivElement>();
+const searchEl = useTemplateRef('searchEl');
+const emojisEl = useTemplateRef('emojisEl');
 
 const {
 	emojiPickerScale,
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 6d7062b41c..4978933f73 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 import { prefer } from '@/preferences.js';
@@ -64,8 +64,8 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
-const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
+const modal = useTemplateRef('modal');
+const picker = useTemplateRef('picker');
 
 function chosen(emoji: string) {
 	emit('done', emoji);
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index 76bb965101..120d5355b2 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 { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
@@ -42,7 +42,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 
 const caption = ref(props.default);
 
diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue
index 554586b2f3..b9888d9b64 100644
--- a/packages/frontend/src/components/MkFoldableSection.vue
+++ b/packages/frontend/src/components/MkFoldableSection.vue
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
 import { miLocalStorage } from '@/local-storage.js';
 import { prefer } from '@/preferences.js';
 import { getBgColor } from '@/utility/get-bg-color.js';
@@ -46,7 +46,7 @@ const props = withDefaults(defineProps<{
 	persistKey: null,
 });
 
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
 const parentBg = ref<string | null>(null);
 // eslint-disable-next-line vue/no-setup-props-reactivity-loss
 const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded);
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 1e2057d28e..b13972f66d 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, ref, shallowRef } from 'vue';
+import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
 import { prefer } from '@/preferences.js';
 import { getBgColor } from '@/utility/get-bg-color.js';
 
@@ -74,7 +74,7 @@ const props = withDefaults(defineProps<{
 	spacerMax: 22,
 });
 
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
 const bgSame = ref(false);
 const opened = ref(props.defaultOpen);
 const openedAtLeastOnce = ref(props.defaultOpen);
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index df39f34816..4756079e76 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -71,7 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { reactive, shallowRef } from 'vue';
+import { reactive, useTemplateRef } from 'vue';
 import MkInput from './MkInput.vue';
 import MkTextarea from './MkTextarea.vue';
 import MkSwitch from './MkSwitch.vue';
@@ -99,7 +99,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 const values = reactive({});
 
 for (const item in props.form) {
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index 8a84bfd541..28bb936755 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, shallowRef, ref } from 'vue';
+import { onMounted, nextTick, watch, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import { misskeyApi } from '@/utility/misskey-api.js';
@@ -35,8 +35,8 @@ const props = withDefaults(defineProps<{
 	label: '',
 });
 
-const rootEl = shallowRef<HTMLDivElement | null>(null);
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const rootEl = useTemplateRef('rootEl');
+const chartEl = useTemplateRef('chartEl');
 const now = new Date();
 let chartInstance: Chart | null = null;
 const fetching = ref(true);
diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue
index ad85660fc4..849136eb8f 100644
--- a/packages/frontend/src/components/MkHorizontalSwipe.vue
+++ b/packages/frontend/src/components/MkHorizontalSwipe.vue
@@ -26,12 +26,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 </div>
 </template>
 <script lang="ts" setup>
-import { ref, shallowRef, computed, nextTick, watch } from 'vue';
+import { ref, useTemplateRef, computed, nextTick, watch } from 'vue';
 import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
 import { isHorizontalSwipeSwiping as isSwiping } from '@/utility/touch.js';
 import { prefer } from '@/preferences.js';
 
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
 
 const tabModel = defineModel<string>('tab');
 
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 6585784e42..b86aca19fd 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -83,7 +83,7 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
 </script>
 
 <script lang="ts" setup>
-import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch, ref } from 'vue';
+import { computed, nextTick, onMounted, onUnmounted, useTemplateRef, watch, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import { render } from 'buraha';
 import { prefer } from '@/preferences.js';
@@ -120,9 +120,9 @@ const props = withDefaults(defineProps<{
 });
 
 const viewId = uuid();
-const canvas = shallowRef<HTMLCanvasElement>();
-const root = shallowRef<HTMLDivElement>();
-const img = shallowRef<HTMLImageElement>();
+const canvas = useTemplateRef('canvas');
+const root = useTemplateRef('root');
+const img = useTemplateRef('img');
 const loaded = ref(false);
 const canvasWidth = ref(64);
 const canvasHeight = ref(64);
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index a66e64311a..49cb5cb544 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, useTemplateRef, watch, computed, toRefs } from 'vue';
 import { debounce } from 'throttle-debounce';
 import { useInterval } from '@@/js/use-interval.js';
 import type { InputHTMLAttributes } from 'vue';
@@ -92,9 +92,9 @@ const focused = ref(false);
 const changed = ref(false);
 const invalid = ref(false);
 const filled = computed(() => v.value !== '' && v.value != null);
-const inputEl = shallowRef<HTMLInputElement>();
-const prefixEl = shallowRef<HTMLElement>();
-const suffixEl = shallowRef<HTMLElement>();
+const inputEl = useTemplateRef('inputEl');
+const prefixEl = useTemplateRef('prefixEl');
+const suffixEl = useTemplateRef('suffixEl');
 const height =
 	props.small ? 33 :
 	props.large ? 39 :
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index 3113c6fad6..76bec9bc66 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -84,8 +84,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, computed, shallowRef } from 'vue';
+import { onMounted, ref, computed, useTemplateRef } from 'vue';
 import { Chart } from 'chart.js';
+import type { HeatmapSource } from '@/components/MkHeatmap.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkChart from '@/components/MkChart.vue';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -95,7 +96,6 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
 import { instance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import MkHeatmap from '@/components/MkHeatmap.vue';
-import type { HeatmapSource } from '@/components/MkHeatmap.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
 import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
@@ -109,8 +109,8 @@ const chartLimit = 500;
 const chartSpan = ref<'hour' | 'day'>('hour');
 const chartSrc = ref('active-users');
 const heatmapSrc = ref<HeatmapSource>('active-users');
-const subDoughnutEl = shallowRef<HTMLCanvasElement>();
-const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
+const subDoughnutEl = useTemplateRef('subDoughnutEl');
+const pubDoughnutEl = useTemplateRef('pubDoughnutEl');
 
 const { handler: externalTooltipHandler1 } = useChartTooltip({
 	position: 'middle',
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 9a78eb67f2..3e5a88a170 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 { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import { navbarItemDef } from '@/navbar.js';
 import { deviceKind } from '@/utility/device-kind.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 = useTemplateRef('modal');
 
 const menu = prefer.s.menu;
 
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue
index 28860d0cf6..db6deb161a 100644
--- a/packages/frontend/src/components/MkMediaAudio.vue
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -88,7 +88,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
+import { useTemplateRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import type { MenuItem } from '@/types/menu.js';
 import type { Keymap } from '@/utility/hotkey.js';
@@ -151,8 +151,8 @@ function hasFocus() {
 	return playerEl.value === document.activeElement || playerEl.value.contains(document.activeElement);
 }
 
-const playerEl = shallowRef<HTMLDivElement>();
-const audioEl = shallowRef<HTMLAudioElement>();
+const playerEl = useTemplateRef('playerEl');
+const audioEl = useTemplateRef('audioEl');
 
 // eslint-disable-next-line vue/no-setup-props-reactivity-loss
 const hide = ref((prefer.s.nsfw === 'force' || prefer.s.dataSaver.media) ? true : (props.audio.isSensitive && prefer.s.nsfw !== 'ignore'));
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 8a7a423869..3c066650d9 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, shallowRef } from 'vue';
+import { computed, onMounted, onUnmounted, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import PhotoSwipeLightbox from 'photoswipe/lightbox';
 import PhotoSwipe from 'photoswipe';
@@ -46,7 +46,7 @@ const props = defineProps<{
 	raw?: boolean;
 }>();
 
-const gallery = shallowRef<HTMLDivElement>();
+const gallery = useTemplateRef('gallery');
 const pswpZIndex = os.claimZIndex('middle');
 document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
 const count = computed(() => props.mediaList.filter(media => previewable(media)).length);
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index 9674cb4461..5475e703a5 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -109,7 +109,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
+import { ref, useTemplateRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import type { MenuItem } from '@/types/menu.js';
 import type { Keymap } from '@/utility/hotkey.js';
@@ -299,8 +299,8 @@ async function toggleSensitive(file: Misskey.entities.DriveFile) {
 }
 
 // MediaControl: Video State
-const videoEl = shallowRef<HTMLVideoElement>();
-const playerEl = shallowRef<HTMLDivElement>();
+const videoEl = useTemplateRef('videoEl');
+const playerEl = useTemplateRef('playerEl');
 const isHoverring = ref(false);
 const controlsShowing = computed(() => {
 	if (!oncePlayed.value) return true;
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index 086573ba6d..f7cd72b6c6 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, provide, shallowRef, watch } from 'vue';
+import { nextTick, onMounted, onUnmounted, provide, useTemplateRef, watch } from 'vue';
 import MkMenu from './MkMenu.vue';
 import type { MenuItem } from '@/types/menu.js';
 
@@ -28,7 +28,7 @@ const emit = defineEmits<{
 
 provide('isNestingMenu', true);
 
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
 const align = 'left';
 
 const SCROLLBAR_THICKNESS = 16;
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index e3c27c5f6e..5438dac3a8 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -179,7 +179,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
-import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, unref, watch } from 'vue';
+import { computed, defineAsyncComponent, inject, nextTick, onBeforeUnmount, onMounted, ref, useTemplateRef, unref, watch } from 'vue';
 import type { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuRadio, MenuRadioOption, MenuParent } from '@/types/menu.js';
 import type { Keymap } from '@/utility/hotkey.js';
 import MkSwitchButton from '@/components/MkSwitch.button.vue';
@@ -212,11 +212,11 @@ const big = isTouchUsing;
 
 const isNestingMenu = inject<boolean>('isNestingMenu', false);
 
-const itemsEl = shallowRef<HTMLElement>();
+const itemsEl = useTemplateRef('itemsEl');
 
 const items2 = ref<InnerMenuItem[]>();
 
-const child = shallowRef<InstanceType<typeof XChild>>();
+const child = useTemplateRef('child');
 
 const keymap = {
 	'up|k|shift+tab': {
@@ -257,7 +257,7 @@ watch(() => props.items, () => {
 });
 
 const childMenu = ref<MenuItem[] | null>();
-const childTarget = shallowRef<HTMLElement | null>();
+const childTarget = useTemplateRef('childTarget');
 
 function closeChild() {
 	childMenu.value = null;
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index 8b3086d55e..b5c93df4ed 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, ref, shallowRef, computed } from 'vue';
+import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch, ref, useTemplateRef, computed } from 'vue';
 import type { Keymap } from '@/utility/hotkey.js';
 import * as os from '@/os.js';
 import { isTouchUsing } from '@/utility/touch.js';
@@ -100,8 +100,8 @@ const maxHeight = ref<number>();
 const fixed = ref(false);
 const transformOrigin = ref('center');
 const showing = ref(true);
-const modalRootEl = shallowRef<HTMLElement>();
-const content = shallowRef<HTMLElement>();
+const modalRootEl = useTemplateRef('modalRootEl');
+const content = useTemplateRef('content');
 const zIndex = os.claimZIndex(props.zPriority);
 const useSendAnime = ref(false);
 const type = computed<ModalTypes>(() => {
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index f06cfffee4..19989e375b 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, shallowRef, ref } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
 import MkModal from './MkModal.vue';
 
 const props = withDefaults(defineProps<{
@@ -47,9 +47,9 @@ const emit = defineEmits<{
 	(event: 'esc'): void;
 }>();
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
-const rootEl = shallowRef<HTMLElement>();
-const headerEl = shallowRef<HTMLElement>();
+const modal = useTemplateRef('modal');
+const rootEl = useTemplateRef('rootEl');
+const headerEl = useTemplateRef('headerEl');
 const bodyWidth = ref(0);
 const bodyHeight = ref(0);
 
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 1f18b173a0..73ff85b150 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -178,7 +178,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, ref, shallowRef, watch, provide } from 'vue';
+import { computed, inject, onMounted, ref, useTemplateRef, watch, provide } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
@@ -271,14 +271,14 @@ if (noteViewInterruptors.length > 0) {
 
 const isRenote = Misskey.note.isPureRenote(note.value);
 
-const rootEl = shallowRef<HTMLElement>();
-const menuButton = shallowRef<HTMLElement>();
-const renoteButton = shallowRef<HTMLElement>();
-const renoteTime = shallowRef<HTMLElement>();
-const reactButton = shallowRef<HTMLElement>();
-const clipButton = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const menuButton = useTemplateRef('menuButton');
+const renoteButton = useTemplateRef('renoteButton');
+const renoteTime = useTemplateRef('renoteTime');
+const reactButton = useTemplateRef('reactButton');
+const clipButton = useTemplateRef('clipButton');
 const appearNote = computed(() => getAppearNote(note.value));
-const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
+const galleryEl = useTemplateRef('galleryEl');
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(false);
 const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 6af8acb2df..4f74432041 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -211,7 +211,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, provide, ref, shallowRef } from 'vue';
+import { computed, inject, onMounted, provide, ref, useTemplateRef } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import { isLink } from '@@/js/is-link.js';
@@ -290,14 +290,14 @@ if (noteViewInterruptors.length > 0) {
 
 const isRenote = Misskey.note.isPureRenote(note.value);
 
-const rootEl = shallowRef<HTMLElement>();
-const menuButton = shallowRef<HTMLElement>();
-const renoteButton = shallowRef<HTMLElement>();
-const renoteTime = shallowRef<HTMLElement>();
-const reactButton = shallowRef<HTMLElement>();
-const clipButton = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
+const menuButton = useTemplateRef('menuButton');
+const renoteButton = useTemplateRef('renoteButton');
+const renoteTime = useTemplateRef('renoteTime');
+const reactButton = useTemplateRef('reactButton');
+const clipButton = useTemplateRef('clipButton');
 const appearNote = computed(() => getAppearNote(note.value));
-const galleryEl = shallowRef<InstanceType<typeof MkMediaList>>();
+const galleryEl = useTemplateRef('galleryEl');
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(false);
 const isDeleted = ref(false);
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index 62d4350400..cee226ee14 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import type { Paging } from '@/components/MkPagination.vue';
 import MkNote from '@/components/MkNote.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
@@ -46,7 +46,7 @@ const props = defineProps<{
 	disableAutoLoad?: boolean;
 }>();
 
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
 
 defineExpose({
 	pagingComponent,
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index a0fb7fea83..d074dceb2f 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -30,13 +30,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef } from 'vue';
-import type { Ref } from 'vue';
+import { ref, useTemplateRef } from 'vue';
+import { notificationTypes } from '@@/js/const.js';
 import MkSwitch from './MkSwitch.vue';
 import MkInfo from './MkInfo.vue';
 import MkButton from './MkButton.vue';
+import type { Ref } from 'vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
-import { notificationTypes } from '@@/js/const.js';
 import { i18n } from '@/i18n.js';
 
 type TypesMap = Record<typeof notificationTypes[number], Ref<boolean>>;
@@ -52,7 +52,7 @@ const props = withDefaults(defineProps<{
 	excludeTypes: () => [],
 });
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 
 const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap);
 
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 9f75a75fb6..08fc846327 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
+import { onUnmounted, onDeactivated, onMounted, computed, useTemplateRef, onActivated } from 'vue';
 import * as Misskey from 'misskey-js';
 import type { notificationTypes } from '@@/js/const.js';
 import MkPagination from '@/components/MkPagination.vue';
@@ -41,7 +41,7 @@ const props = defineProps<{
 	excludeTypes?: typeof notificationTypes[number][];
 }>();
 
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
 
 const pagination = computed(() => prefer.r.useGroupedNotifications.value ? {
 	endpoint: 'i/notifications-grouped' as const,
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index a05176e2f4..04276b47fe 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, shallowRef, ref } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef, ref } from 'vue';
 import { i18n } from '@/i18n.js';
 
 const props = withDefaults(defineProps<{
@@ -22,7 +22,7 @@ const props = withDefaults(defineProps<{
 	maxHeight: 200,
 });
 
-const content = shallowRef<HTMLElement>();
+const content = useTemplateRef('content');
 const omitted = ref(false);
 const ignoreOmit = ref(false);
 
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 4fbdc51d22..5cb00c5292 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
+import { computed, onMounted, onUnmounted, provide, ref, useTemplateRef } from 'vue';
 import { url } from '@@/js/config.js';
 import type { PageMetadata } from '@/page.js';
 import RouterView from '@/components/global/RouterView.vue';
@@ -57,7 +57,7 @@ const emit = defineEmits<{
 const windowRouter = createRouter(props.initialPath);
 
 const pageMetadata = ref<null | PageMetadata>(null);
-const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
+const windowEl = useTemplateRef('windowEl');
 const history = ref<{ path: string; }[]>([{
 	path: windowRouter.getCurrentFullPath(),
 }]);
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index 071dbba5eb..6a1a91a9f4 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, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
+import { computed, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, useTemplateRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { useDocumentVisibility } from '@@/js/use-document-visibility.js';
 import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@@/js/scroll.js';
@@ -106,7 +106,7 @@ const emit = defineEmits<{
 	(ev: 'status', error: boolean): void;
 }>();
 
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
 
 // 遡り中かどうか
 const backed = ref(false);
diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue
index 2068ca39a1..2abf8669ed 100644
--- a/packages/frontend/src/components/MkPasswordDialog.vue
+++ b/packages/frontend/src/components/MkPasswordDialog.vue
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -54,8 +54,8 @@ const emit = defineEmits<{
 	(ev: 'cancelled'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
-const passwordInput = shallowRef<InstanceType<typeof MkInput>>();
+const dialog = useTemplateRef('dialog');
+const passwordInput = useTemplateRef('passwordInput');
 const password = ref('');
 const isBackupCode = ref(false);
 const token = ref<string | null>(null);
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index df664e49f7..232cc005e1 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, shallowRef } from 'vue';
+import { ref, useTemplateRef } from 'vue';
 import MkModal from './MkModal.vue';
 import MkMenu from './MkMenu.vue';
 import type { MenuItem } from '@/types/menu.js';
@@ -28,7 +28,7 @@ const emit = defineEmits<{
 	(ev: 'closing'): void;
 }>();
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
 const manualShowing = ref(true);
 const hiding = ref(false);
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 6e941f75be..0d37d973f0 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -99,7 +99,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, useTemplateRef } from 'vue';
 import * as mfm from 'mfm-js';
 import * as Misskey from 'misskey-js';
 import insertTextAtCursor from 'insert-text-at-cursor';
@@ -165,11 +165,11 @@ 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>();
-const otherSettingsButton = shallowRef<HTMLElement>();
+const textareaEl = useTemplateRef('textareaEl');
+const cwInputEl = useTemplateRef('cwInputEl');
+const hashtagsInputEl = useTemplateRef('hashtagsInputEl');
+const visibilityButton = useTemplateRef('visibilityButton');
+const otherSettingsButton = useTemplateRef('otherSettingsButton');
 
 const posting = ref(false);
 const posted = ref(false);
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index 4bef5824fe..c467e29df6 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -25,10 +25,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
+import type { PostFormProps } from '@/types/post-form.js';
 import MkModal from '@/components/MkModal.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
-import type { PostFormProps } from '@/types/post-form.js';
 
 const props = withDefaults(defineProps<PostFormProps & {
 	instant?: boolean;
@@ -42,8 +42,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
-const form = shallowRef<InstanceType<typeof MkPostForm>>();
+const modal = useTemplateRef('modal');
 
 function onPosted() {
 	modal.value?.close({
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index af4e2a4fe0..1fbf00d212 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -23,9 +23,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
-import { i18n } from '@/i18n.js';
+import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
 import { getScrollContainer } from '@@/js/scroll.js';
+import { i18n } from '@/i18n.js';
 import { isHorizontalSwipeSwiping } from '@/utility/touch.js';
 
 const SCROLL_STOP = 10;
@@ -43,7 +43,7 @@ const pullDistance = ref(0);
 let supportPointerDesktop = false;
 let startScreenY: number | null = null;
 
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
 let scrollEl: HTMLElement | null = null;
 
 let disabled = false;
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue
index 8e5be38e03..118dbbe15a 100644
--- a/packages/frontend/src/components/MkRange.vue
+++ b/packages/frontend/src/components/MkRange.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, defineAsyncComponent, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
 import { isTouchUsing } from '@/utility/touch.js';
 import * as os from '@/os.js';
 
@@ -58,8 +58,8 @@ const emit = defineEmits<{
 	(ev: 'dragEnded', value: number): void;
 }>();
 
-const containerEl = shallowRef<HTMLElement>();
-const thumbEl = shallowRef<HTMLElement>();
+const containerEl = useTemplateRef('containerEl');
+const thumbEl = useTemplateRef('thumbEl');
 
 const rawValue = ref((props.modelValue - props.min) / (props.max - props.min));
 const steppedRawValue = computed(() => {
diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue
index 7551524246..453253f0fc 100644
--- a/packages/frontend/src/components/MkReactionIcon.vue
+++ b/packages/frontend/src/components/MkReactionIcon.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, shallowRef } from 'vue';
+import { defineAsyncComponent, useTemplateRef } from 'vue';
 import { useTooltip } from '@/use/use-tooltip.js';
 import * as os from '@/os.js';
 
@@ -20,7 +20,7 @@ const props = defineProps<{
 	withTooltip?: boolean;
 }>();
 
-const elRef = shallowRef();
+const elRef = useTemplateRef('elRef');
 
 if (props.withTooltip) {
 	useTooltip(elRef, (showing) => {
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 062894314d..590b0f6f19 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, shallowRef, watch } from 'vue';
+import { computed, inject, onMounted, useTemplateRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { getUnicodeEmoji } from '@@/js/emojilist.js';
 import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
@@ -50,7 +50,7 @@ const emit = defineEmits<{
 	(ev: 'reactionToggled', emoji: string, newCount: number): void;
 }>();
 
-const buttonEl = shallowRef<HTMLElement>();
+const buttonEl = useTemplateRef('buttonEl');
 
 const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
 const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index a962b37e4a..1ab2397337 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, shallowRef, ref } from 'vue';
+import { onMounted, nextTick, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { store } from '@/store.js';
@@ -23,8 +23,8 @@ import { initChart } from '@/utility/init-chart.js';
 
 initChart();
 
-const rootEl = shallowRef<HTMLDivElement | null>(null);
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const rootEl = useTemplateRef('rootEl');
+const chartEl = useTemplateRef('chartEl');
 let chartInstance: Chart | null = null;
 const fetching = ref(true);
 
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index 6fa19efed5..60b3f17b14 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
 import { Chart } from 'chart.js';
 import tinycolor from 'tinycolor2';
 import { store } from '@/store.js';
@@ -20,7 +20,7 @@ import { misskeyApi } from '@/utility/misskey-api.js';
 
 initChart();
 
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const chartEl = useTemplateRef('chartEl');
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index a970d1ce00..f122da7468 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { shallowRef } from 'vue';
+import { useTemplateRef } from 'vue';
 import type { OpenOnRemoteOptions } from '@/utility/please-login.js';
 import MkSignin from '@/components/MkSignin.vue';
 import MkModal from '@/components/MkModal.vue';
@@ -46,7 +46,7 @@ const emit = defineEmits<{
 	(ev: 'cancelled'): void;
 }>();
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
 
 function onClose() {
 	emit('cancelled');
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 6fb9d77837..5c9047dd43 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XSignup from '@/components/MkSignupDialog.form.vue';
 import XServerRules from '@/components/MkSignupDialog.rules.vue';
@@ -52,7 +52,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 
 const isAcceptedServerRule = ref(false);
 
diff --git a/packages/frontend/src/components/MkSparkle.vue b/packages/frontend/src/components/MkSparkle.vue
index b3fc67c0df..2400c5ec7f 100644
--- a/packages/frontend/src/components/MkSparkle.vue
+++ b/packages/frontend/src/components/MkSparkle.vue
@@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
+import { onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
 
 const particles = ref<{
 	id: string,
@@ -66,7 +66,7 @@ const particles = ref<{
 	dur: number,
 	color: string
 }[]>([]);
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
 const width = ref(0);
 const height = ref(0);
 const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];
diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue
index 2e1a96c326..f819f82923 100644
--- a/packages/frontend/src/components/MkSystemWebhookEditor.vue
+++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue
@@ -92,15 +92,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
+import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkInput from '@/components/MkInput.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
 import type {
 	MkSystemWebhookEditorProps,
 	MkSystemWebhookResult,
 	SystemWebhookEventType,
 } from '@/components/MkSystemWebhookEditor.impl.js';
+import MkInput from '@/components/MkInput.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
 import { i18n } from '@/i18n.js';
 import MkButton from '@/components/MkButton.vue';
 import { misskeyApi } from '@/utility/misskey-api.js';
@@ -122,7 +122,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
 
 const props = defineProps<MkSystemWebhookEditorProps>();
 
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index 87aa046963..8ad9e14015 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, ref, shallowRef } from 'vue';
+import { onMounted, watch, onBeforeUnmount, ref, useTemplateRef } from 'vue';
 import tinycolor from 'tinycolor2';
 
 const loaded = !!window.TagCanvas;
@@ -24,9 +24,9 @@ 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('');
 const available = ref(false);
-const rootEl = shallowRef<HTMLElement | null>(null);
-const canvasEl = shallowRef<HTMLCanvasElement | null>(null);
-const tagsEl = shallowRef<HTMLElement | null>(null);
+const rootEl = useTemplateRef('rootEl');
+const canvasEl = useTemplateRef('canvasEl');
+const tagsEl = useTemplateRef('tagsEl');
 const width = ref(300);
 
 watch(available, () => {
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index c9da226734..14ffdae663 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs, useTemplateRef } from 'vue';
 import { debounce } from 'throttle-debounce';
 import type { SuggestionType } from '@/utility/autocomplete.js';
 import MkButton from '@/components/MkButton.vue';
@@ -75,7 +75,7 @@ const focused = ref(false);
 const changed = ref(false);
 const invalid = ref(false);
 const filled = computed(() => v.value !== '' && v.value != null);
-const inputEl = shallowRef<HTMLTextAreaElement>();
+const inputEl = useTemplateRef('inputEl');
 const preview = ref(false);
 let autocompleteWorker: Autocomplete | null = null;
 
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index e8b740eae2..73057e4644 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, ref, shallowRef } from 'vue';
+import { computed, watch, onUnmounted, provide, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import type { BasicTimelineType } from '@/timelines.js';
 import type { Paging } from '@/components/MkPagination.vue';
@@ -67,8 +67,8 @@ type TimelineQueryType = {
 	roleId?: string
 };
 
-const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
-const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
+const prComponent = useTemplateRef('prComponent');
+const tlComponent = useTemplateRef('tlComponent');
 
 let tlNotesCount = 0;
 
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index bed15031cb..b449155edb 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkInput from './MkInput.vue';
 import MkSwitch from './MkSwitch.vue';
@@ -77,7 +77,7 @@ const emit = defineEmits<{
 const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
 const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin'));
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 const name = ref(props.initialName);
 const permissionSwitches = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);
 const permissionSwitchesForAdmin = ref({} as Record<(typeof Misskey.permissions)[number], boolean>);
diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue
index 8031abd563..ddfa6def87 100644
--- a/packages/frontend/src/components/MkTooltip.vue
+++ b/packages/frontend/src/components/MkTooltip.vue
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, shallowRef } from 'vue';
+import { nextTick, onMounted, onUnmounted, useTemplateRef } from 'vue';
 import * as os from '@/os.js';
 import { calcPopupPosition } from '@/utility/popup-position.js';
 import { prefer } from '@/preferences.js';
@@ -51,7 +51,7 @@ const emit = defineEmits<{
 // タイミングによっては最初から showing = false な場合があり、その場合に closed 扱いにしないと永久にDOMに残ることになる
 if (!props.showing) emit('closed');
 
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
 const zIndex = os.claimZIndex('high');
 
 function setPosition() {
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index 5908db33c4..3e91baada4 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -148,7 +148,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, watch } from 'vue';
+import { ref, useTemplateRef, watch } from 'vue';
+import { host } from '@@/js/config.js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import XNote from '@/components/MkTutorialDialog.Note.vue';
@@ -158,7 +159,6 @@ import XSensitive from '@/components/MkTutorialDialog.Sensitive.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { host } from '@@/js/config.js';
 import { claimAchievement } from '@/utility/achievements.js';
 import * as os from '@/os.js';
 
@@ -170,7 +170,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 
 // eslint-disable-next-line vue/no-setup-props-reactivity-loss
 const page = ref(props.initialPage ?? 0);
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 95732d5796..79ab464cb0 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -15,15 +15,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
+import { version } from '@@/js/config.js';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
-import { version } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { confetti } from '@/utility/confetti.js';
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
 
 function whatIsNew() {
 	modal.value?.close();
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 6bf3eb44dc..057af49a36 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, computed, shallowRef } from 'vue';
+import { onMounted, ref, computed, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import { host as currentHost, hostname } from '@@/js/config.js';
 import MkInput from '@/components/MkInput.vue';
@@ -94,7 +94,7 @@ const host = ref('');
 const users = ref<Misskey.entities.UserLite[]>([]);
 const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
 const selected = ref<Misskey.entities.UserLite | null>(null);
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
 
 function search() {
 	if (username.value === '' && host.value === '') {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index eb3a69217e..767f5c591a 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -128,7 +128,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, watch, nextTick, defineAsyncComponent } from 'vue';
+import { ref, useTemplateRef, watch, nextTick, defineAsyncComponent } from 'vue';
+import { host } from '@@/js/config.js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
@@ -137,7 +138,6 @@ import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
 import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { host } from '@@/js/config.js';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 import { store } from '@/store.js';
 import * as os from '@/os.js';
@@ -146,9 +146,8 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
-
-// eslint-disable-next-line vue/no-setup-props-reactivity-loss
+const dialog = useTemplateRef('dialog');
+ 
 const page = ref(store.s.accountSetupWizard);
 
 watch(page, () => {
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 650e639c4f..cb402b1a57 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, shallowRef, ref } from 'vue';
+import { nextTick, useTemplateRef, 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 = useTemplateRef('modal');
 
 const props = withDefaults(defineProps<{
 	currentVisibility: typeof Misskey.noteVisibilities[number];
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
index 4168876f08..2c2c515032 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, shallowRef, ref, nextTick } from 'vue';
+import { onMounted, useTemplateRef, ref, nextTick } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import tinycolor from 'tinycolor2';
@@ -25,7 +25,7 @@ import { initChart } from '@/utility/init-chart.js';
 
 initChart();
 
-const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+const chartEl = useTemplateRef('chartEl');
 const now = new Date();
 let chartInstance: Chart | null = null;
 const chartLimit = 30;
diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue
index 34fa6b0723..282da00ee1 100644
--- a/packages/frontend/src/components/MkWaitingDialog.vue
+++ b/packages/frontend/src/components/MkWaitingDialog.vue
@@ -14,10 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch, shallowRef } from 'vue';
+import { watch, useTemplateRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 
-const modal = shallowRef<InstanceType<typeof MkModal>>();
+const modal = useTemplateRef('modal');
 
 const props = defineProps<{
 	success: boolean;
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index 4b3c728ee4..98ec448a8e 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, shallowRef, ref } from 'vue';
+import { onBeforeUnmount, onMounted, provide, useTemplateRef, ref } from 'vue';
 import type { MenuItem } from '@/types/menu.js';
 import contains from '@/utility/contains.js';
 import * as os from '@/os.js';
@@ -114,7 +114,7 @@ const emit = defineEmits<{
 
 provide('inWindow', true);
 
-const rootEl = shallowRef<HTMLElement | null>();
+const rootEl = useTemplateRef('rootEl');
 const showing = ref(true);
 let beforeClickedAt = 0;
 const maximized = ref(false);
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 3403418991..336160ec17 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -14,7 +14,7 @@ export type MkABehavior = 'window' | 'browser' | null;
 </script>
 
 <script lang="ts" setup>
-import { computed, inject, shallowRef } from 'vue';
+import { computed, inject, useTemplateRef } from 'vue';
 import { url } from '@@/js/config.js';
 import * as os from '@/os.js';
 import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
@@ -32,7 +32,7 @@ const props = withDefaults(defineProps<{
 
 const behavior = props.behavior ?? inject<MkABehavior>('linkNavigationBehavior', null);
 
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
 
 defineExpose({ $el: el });
 
diff --git a/packages/frontend/src/components/global/MkLazy.vue b/packages/frontend/src/components/global/MkLazy.vue
index f35932ae77..b352296469 100644
--- a/packages/frontend/src/components/global/MkLazy.vue
+++ b/packages/frontend/src/components/global/MkLazy.vue
@@ -11,9 +11,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, shallowRef } from 'vue';
+import { nextTick, onMounted, onActivated, onBeforeUnmount, ref, useTemplateRef } from 'vue';
 
-const rootEl = shallowRef<HTMLDivElement>();
+const rootEl = useTemplateRef('rootEl');
 const showing = ref(false);
 
 const observer = new IntersectionObserver(
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index 8a5e556293..81adc07f26 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -53,7 +53,7 @@ export type Tab = {
 </script>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
+import { nextTick, onMounted, onUnmounted, useTemplateRef, watch } from 'vue';
 import { prefer } from '@/preferences.js';
 
 const props = withDefaults(defineProps<{
@@ -69,9 +69,9 @@ const emit = defineEmits<{
 	(ev: 'tabClick', key: string);
 }>();
 
-const el = shallowRef<HTMLElement | null>(null);
+const el = useTemplateRef('el');
+const tabHighlightEl = useTemplateRef('tabHighlightEl');
 const tabRefs: Record<string, HTMLElement | null> = {};
-const tabHighlightEl = shallowRef<HTMLElement | null>(null);
 
 function onTabMousedown(tab: Tab, ev: MouseEvent): void {
 	// ユーザビリティの観点からmousedown時にはonClickは呼ばない
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index b353269fef..4321d69253 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, shallowRef, computed } from 'vue';
+import { onMounted, onUnmounted, ref, inject, useTemplateRef, computed } from 'vue';
 import tinycolor from 'tinycolor2';
 import { scrollToTop } from '@@/js/scroll.js';
 import XTabs from './MkPageHeader.tabs.vue';
@@ -75,7 +75,7 @@ const pageMetadata = computed(() => props.overridePageMetadata ?? injectedPageMe
 const hideTitle = computed(() => inject('shouldOmitHeaderTitle', false) || props.hideTitle);
 const thin_ = props.thin || inject('shouldHeaderThin', false);
 
-const el = shallowRef<HTMLElement | undefined>(undefined);
+const el = useTemplateRef('el');
 const bg = ref<string | undefined>(undefined);
 const narrow = ref(false);
 const hasTabs = computed(() => props.tabs.length > 0);
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 7ee3952083..7dda4b2f8a 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -23,9 +23,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, provide, inject, ref, watch, useTemplateRef } from 'vue';
-import type { Ref } from 'vue';
-
 import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js';
+import type { Ref } from 'vue';
 
 const rootEl = useTemplateRef('rootEl');
 const headerEl = useTemplateRef('headerEl');
diff --git a/packages/frontend/src/components/grid/MkDataCell.vue b/packages/frontend/src/components/grid/MkDataCell.vue
index f7f6f5c140..7c8a5d64d7 100644
--- a/packages/frontend/src/components/grid/MkDataCell.vue
+++ b/packages/frontend/src/components/grid/MkDataCell.vue
@@ -39,10 +39,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 					{{ cell.value }}
 				</div>
 				<div v-else-if="cellType === 'boolean'">
-					<div :class="[$style.bool, {
-						[$style.boolTrue]: cell.value === true,
-						'ti ti-check': cell.value === true,
-					}]"></div>
+					<div
+						:class="[$style.bool, {
+							[$style.boolTrue]: cell.value === true,
+							'ti ti-check': cell.value === true,
+						}]"
+					></div>
 				</div>
 				<div v-else-if="cellType === 'image'">
 					<img
@@ -88,14 +90,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, shallowRef, toRefs, watch } from 'vue';
+import { computed, defineAsyncComponent, nextTick, onMounted, onUnmounted, ref, useTemplateRef, toRefs, watch } from 'vue';
+import type { Size } from '@/components/grid/grid.js';
+import type { CellValue, GridCell } from '@/components/grid/cell.js';
+import type { GridRowSetting } from '@/components/grid/row.js';
 import { GridEventEmitter } from '@/components/grid/grid.js';
 import { useTooltip } from '@/use/use-tooltip.js';
 import * as os from '@/os.js';
 import { equalCellAddress, getCellAddress } from '@/components/grid/grid-utils.js';
-import type { Size } from '@/components/grid/grid.js';
-import type { CellValue, GridCell } from '@/components/grid/cell.js';
-import type { GridRowSetting } from '@/components/grid/row.js';
 
 const emit = defineEmits<{
 	(ev: 'operation:beginEdit', sender: GridCell): void;
@@ -111,9 +113,9 @@ const props = defineProps<{
 
 const { cell, bus } = toRefs(props);
 
-const rootEl = shallowRef<InstanceType<typeof HTMLTableCellElement>>();
-const contentAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
-const inputAreaEl = shallowRef<InstanceType<typeof HTMLDivElement>>();
+const rootEl = useTemplateRef('rootEl');
+const contentAreaEl = useTemplateRef('contentAreaEl');
+const inputAreaEl = useTemplateRef('inputAreaEl');
 
 /** 値が編集中かどうか */
 const editing = ref<boolean>(false);
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 36dac2954d..2d0135e6a6 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -134,7 +134,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onBeforeUnmount, ref, shallowRef, computed } from 'vue';
+import { nextTick, onBeforeUnmount, ref, useTemplateRef, computed } from 'vue';
 import { version } from '@@/js/config.js';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
@@ -403,7 +403,7 @@ const easterEggEmojis = ref<{
 	emoji: string
 }[]>([]);
 const easterEggEngine = ref<{ stop: () => void } | null>(null);
-const containerEl = shallowRef<HTMLElement>();
+const containerEl = useTemplateRef('containerEl');
 
 function iconLoaded() {
 	const emojis = prefer.s.emojiPalettes[0].emojis;
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index f073a4af7e..819ca2c127 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, ref, shallowRef, watch, nextTick, inject } from 'vue';
+import { computed, onMounted, onUnmounted, ref, useTemplateRef, watch, nextTick, inject } from 'vue';
 import tinycolor from 'tinycolor2';
 import { scrollToTop } from '@@/js/scroll.js';
 import { popupMenu } from '@/os.js';
@@ -68,9 +68,9 @@ const emit = defineEmits<{
 
 const pageMetadata = inject(DI.pageMetadata);
 
-const el = shallowRef<HTMLElement>(null);
+const el = useTemplateRef('el');
+const tabHighlightEl = useTemplateRef('tabHighlightEl');
 const tabRefs = {};
-const tabHighlightEl = shallowRef<HTMLElement | null>(null);
 const bg = ref<string | null>(null);
 const height = ref(0);
 const hasTabs = computed(() => {
diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
index 5b049e5bad..a56a24ff7d 100644
--- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
+++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue
@@ -71,8 +71,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, ref, shallowRef, toRefs } from 'vue';
+import { computed, onMounted, ref, useTemplateRef, toRefs } from 'vue';
 import { entities } from 'misskey-js';
+import type { MkSystemWebhookResult } from '@/components/MkSystemWebhookEditor.impl.js';
 import MkButton from '@/components/MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n.js';
@@ -80,7 +81,6 @@ import MkInput from '@/components/MkInput.vue';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import MkSelect from '@/components/MkSelect.vue';
 import { showSystemWebhookEditorDialog } from '@/components/MkSystemWebhookEditor.impl.js';
-import type { MkSystemWebhookResult } from '@/components/MkSystemWebhookEditor.impl.js';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkDivider from '@/components/MkDivider.vue';
 import * as os from '@/os.js';
@@ -100,7 +100,7 @@ const props = defineProps<{
 
 const { mode, id } = toRefs(props);
 
-const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialogEl = useTemplateRef('dialogEl');
 
 const loading = ref<number>(0);
 
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index e4119bb62d..08e06ce4b4 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -59,7 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, shallowRef, ref } from 'vue';
+import { computed, useTemplateRef, ref } from 'vue';
 import XHeader from './_header_.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkPagination from '@/components/MkPagination.vue';
@@ -70,7 +70,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { store } from '@/store.js';
 
-const reports = shallowRef<InstanceType<typeof MkPagination>>();
+const reports = useTemplateRef('reports');
 
 const state = ref('unresolved');
 const reporterOrigin = ref('combined');
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
index 069ed6e1f3..6e6476b027 100644
--- a/packages/frontend/src/pages/admin/invites.vue
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -55,8 +55,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, shallowRef } from 'vue';
+import { computed, ref, useTemplateRef } from 'vue';
 import XHeader from './_header_.vue';
+import type { Paging } from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
@@ -66,11 +67,10 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkPagination from '@/components/MkPagination.vue';
-import type { Paging } from '@/components/MkPagination.vue';
 import MkInviteCode from '@/components/MkInviteCode.vue';
 import { definePage } from '@/page.js';
 
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
 
 const type = ref('all');
 const sort = ref('+createdAt');
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue
index ae7022a1c9..1fb2c4b726 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, shallowRef, ref } from 'vue';
+import { computed, useTemplateRef, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XHeader from './_header_.vue';
 import XModLog from './modlog.ModLog.vue';
@@ -41,7 +41,7 @@ import { i18n } from '@/i18n.js';
 import { definePage } from '@/page.js';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 
-const logs = shallowRef<InstanceType<typeof MkPagination>>();
+const logs = useTemplateRef('logs');
 
 const type = ref<string | null>(null);
 const moderatorId = ref('');
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index 5c50336b2c..5b7f669f6b 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, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import { misskeyApi } from '@/utility/misskey-api.js';
@@ -24,7 +24,7 @@ import { initChart } from '@/utility/init-chart.js';
 
 initChart();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 7;
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index 1949201ca0..4c06d94d6d 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, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import isChromatic from 'chromatic';
@@ -34,8 +34,8 @@ import { initChart } from '@/utility/init-chart.js';
 initChart();
 
 const chartLimit = 50;
-const chartEl = shallowRef<HTMLCanvasElement>();
-const chartEl2 = shallowRef<HTMLCanvasElement>();
+const chartEl = useTemplateRef('chartEl');
+const chartEl2 = useTemplateRef('chartEl2');
 const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue
index 424bcdd51f..32dd981ca9 100644
--- a/packages/frontend/src/pages/admin/overview.pie.vue
+++ b/packages/frontend/src/pages/admin/overview.pie.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
 import { Chart } from 'chart.js';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
 import { initChart } from '@/utility/init-chart.js';
@@ -26,7 +26,7 @@ const props = defineProps<{
 	data: InstanceForPie[];
 }>();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
 
 const { handler: externalTooltipHandler } = useChartTooltip({
 	position: 'middle',
diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue
index 34c0945ddb..6fc941a848 100644
--- a/packages/frontend/src/pages/admin/overview.queue.chart.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
 import { Chart } from 'chart.js';
 import { store } from '@/store.js';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -22,7 +22,7 @@ const props = defineProps<{
 	type: string;
 }>();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index de6b254412..cf07cddced 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, shallowRef } from 'vue';
+import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XChart from './overview.queue.chart.vue';
 import type { ApQueueDomain } from '@/pages/admin/queue.vue';
@@ -48,10 +48,10 @@ const activeSincePrevTick = ref(0);
 const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
-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 chartProcess = useTemplateRef('chartProcess');
+const chartActive = useTemplateRef('chartActive');
+const chartDelayed = useTemplateRef('chartDelayed');
+const chartWaiting = useTemplateRef('chartWaiting');
 
 const props = defineProps<{
 	domain: ApQueueDomain;
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index c23662572a..616815a6a6 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, shallowRef, ref, computed } from 'vue';
+import { markRaw, onMounted, onBeforeUnmount, nextTick, shallowRef, ref, computed, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XFederation from './overview.federation.vue';
 import XInstances from './overview.instances.vue';
@@ -85,7 +85,7 @@ import { i18n } from '@/i18n.js';
 import { definePage } from '@/page.js';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 
-const rootEl = shallowRef<HTMLElement>();
+const rootEl = useTemplateRef('rootEl');
 const serverInfo = ref<Misskey.entities.ServerInfoResponse | null>(null);
 const topSubInstancesForPie = ref<InstanceForPie[] | null>(null);
 const topPubInstancesForPie = ref<InstanceForPie[] | null>(null);
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue
index 9c7a83b1fb..5dd2887024 100644
--- a/packages/frontend/src/pages/admin/queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef } from 'vue';
+import { onMounted, useTemplateRef } from 'vue';
 import { Chart } from 'chart.js';
 import { store } from '@/store.js';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -22,7 +22,7 @@ const props = defineProps<{
 	type: string;
 }>();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = useTemplateRef('chartEl');
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 607a974d20..1ba02d6e0e 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, shallowRef } from 'vue';
+import { markRaw, onMounted, onUnmounted, ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XChart from './queue.chart.chart.vue';
 import type { ApQueueDomain } from '@/pages/admin/queue.vue';
@@ -65,10 +65,10 @@ const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
 const jobs = ref<Misskey.Endpoints[`admin/queue/${ApQueueDomain}-delayed`]['res']>([]);
-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 chartProcess = useTemplateRef('chartProcess');
+const chartActive = useTemplateRef('chartActive');
+const chartDelayed = useTemplateRef('chartDelayed');
+const chartWaiting = useTemplateRef('chartWaiting');
 
 const props = defineProps<{
 	domain: ApQueueDomain;
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 1998144c05..a44951a947 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -60,7 +60,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, shallowRef, ref, watchEffect } from 'vue';
+import { computed, useTemplateRef, ref, watchEffect } from 'vue';
 import XHeader from './_header_.vue';
 import { defaultMemoryStorage } from '@/memory-storage';
 import MkButton from '@/components/MkButton.vue';
@@ -82,7 +82,7 @@ type SearchQuery = {
 	hostname?: string;
 };
 
-const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const paginationComponent = useTemplateRef('paginationComponent');
 const storedQuery = JSON.parse(defaultMemoryStorage.getItem('admin-users-query') ?? '{}') as SearchQuery;
 
 const sort = ref(storedQuery.sort ?? '+createdAt');
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index f0587a5ca0..5f98ed798f 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -24,10 +24,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, ref, shallowRef } from 'vue';
+import { computed, watch, ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkTimeline from '@/components/MkTimeline.vue';
 import { scroll } from '@@/js/scroll.js';
+import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { definePage } from '@/page.js';
@@ -42,8 +42,8 @@ const props = defineProps<{
 
 const antenna = ref<Misskey.entities.Antenna | null>(null);
 const queue = ref(0);
-const rootEl = shallowRef<HTMLElement>();
-const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
+const rootEl = useTemplateRef('rootEl');
+const tlEl = useTemplateRef('tlEl');
 
 function queueUpdated(q) {
 	queue.value = q;
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index 46e7a3d05e..c714ccb003 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, ref, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, ref, useTemplateRef } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkPagination from '@/components/MkPagination.vue';
@@ -86,7 +86,7 @@ import { getProxiedImageUrl } from '@/utility/media-proxy.js';
 import { i18n } from '@/i18n.js';
 import { definePage } from '@/page.js';
 
-const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const emojisPaginationComponent = useTemplateRef('emojisPaginationComponent');
 
 const tab = ref('local');
 const query = ref<string | null>(null);
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
index e30e381ce1..baae888a94 100644
--- a/packages/frontend/src/pages/drop-and-fusion.game.vue
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -191,7 +191,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch, useTemplateRef } from 'vue';
 import * as Matter from 'matter-js';
 import * as Misskey from 'misskey-js';
 import { DropAndFusionGame } from 'misskey-bubble-game';
@@ -567,8 +567,8 @@ let game = new DropAndFusionGame({
 });
 attachGameEvents();
 
-const containerEl = shallowRef<HTMLElement>();
-const canvasEl = shallowRef<HTMLCanvasElement>();
+const containerEl = useTemplateRef('containerEl');
+const canvasEl = useTemplateRef('canvasEl');
 const dropperX = ref(0);
 const currentPick = shallowRef<{ id: string; mono: Mono } | null>(null);
 const stock = shallowRef<{ id: string; mono: Mono }[]>([]);
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index c112045a21..c0618b9fce 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, ref, shallowRef, computed } from 'vue';
+import { watch, ref, useTemplateRef, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkUserList from '@/components/MkUserList.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
@@ -77,7 +77,7 @@ const props = defineProps<{
 }>();
 
 const origin = ref('local');
-const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
+const tagsEl = useTemplateRef('tagsEl');
 const tagsLocal = ref<Misskey.entities.Hashtag[]>([]);
 const tagsRemote = ref<Misskey.entities.Hashtag[]>([]);
 
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index d24ebe6aae..b76f6a6033 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, ref, shallowRef } from 'vue';
+import { computed, watch, ref, useTemplateRef } from 'vue';
 import XFeatured from './explore.featured.vue';
 import XUsers from './explore.users.vue';
 import XRoles from './explore.roles.vue';
@@ -38,7 +38,7 @@ const props = withDefaults(defineProps<{
 });
 
 const tab = ref(props.initialTab);
-const tagsEl = shallowRef<InstanceType<typeof MkFoldableSection>>();
+const tagsEl = useTemplateRef('tagsEl');
 
 watch(() => props.tag, () => {
 	if (tagsEl.value) tagsEl.value.toggleContent(props.tag == null);
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index f44d2364af..1ea501c34c 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { shallowRef, computed, ref } from 'vue';
+import { useTemplateRef, computed, ref } from 'vue';
 import type { Paging } from '@/components/MkPagination.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -57,7 +57,7 @@ import { infoImageUrl } from '@/instance.js';
 import { $i } from '@/i.js';
 import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 
-const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const paginationComponent = useTemplateRef('paginationComponent');
 
 const pagination = computed<Paging>(() => tab.value === 'list' ? {
 	endpoint: 'following/requests/list',
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index a6f0dfcf4b..9181e7f2ef 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, ref, shallowRef } from 'vue';
+import { computed, ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import type { Paging } from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n.js';
@@ -47,7 +47,7 @@ import { definePage } from '@/page.js';
 import { serverErrorImageUrl, instance } from '@/instance.js';
 import { $i } from '@/i.js';
 
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
 const currentInviteLimit = ref<null | number>(null);
 const inviteLimit = (($i != null && $i.policies.inviteLimit) || (($i == null && instance.policies.inviteLimit))) as number;
 const inviteLimitCycle = (($i != null && $i.policies.inviteLimitCycle) || ($i == null && instance.policies.inviteLimitCycle)) as number;
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index 247720d9ac..d354221ca3 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { watch, ref, shallowRef, computed } from 'vue';
+import { watch, ref, useTemplateRef, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -46,7 +46,7 @@ const tab = ref('my');
 
 const favorites = ref<Misskey.entities.Clip[] | null>(null);
 
-const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const pagingComponent = useTemplateRef('pagingComponent');
 
 watch(tab, async () => {
 	favorites.value = await misskeyApi('clips/my-favorites');
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 a8b4da2a06..4a980ce472 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
@@ -15,8 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-
-import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue';
+import { watch, ref, useTemplateRef, onMounted, onUnmounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import XContainer from '../page-editor.container.vue';
 import { i18n } from '@/i18n.js';
@@ -33,7 +32,7 @@ const emit = defineEmits<{
 let autocomplete: Autocomplete;
 
 const text = ref(props.modelValue.text ?? '');
-const inputEl = shallowRef<HTMLTextAreaElement | null>(null);
+const inputEl = useTemplateRef('inputEl');
 
 watch(text, () => {
 	emit('update:modelValue', {
diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
index c88f7fbe15..2a07a9a21c 100644
--- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue
+++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
@@ -106,7 +106,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
+import { useTemplateRef, ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
@@ -132,7 +132,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 const page = ref(0);
 const token = ref<string | number | null>(null);
 const backupCodes = ref<string[]>();
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
index 335ecedfa8..0f02d95d71 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, ref, computed } from 'vue';
+import { useTemplateRef, ref, computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -82,7 +82,7 @@ const emit = defineEmits<{
 	(ev: 'detach'): void;
 }>();
 
-const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const dialog = useTemplateRef('dialog');
 const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0);
 const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id)));
 const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0);
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 89dc9581c2..c315e9289a 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, onActivated, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue';
 import type { PageMetadata } from '@/page.js';
 import type { SuperMenuDef } from '@/components/MkSuperMenu.vue';
 import { i18n } from '@/i18n.js';
@@ -56,7 +56,7 @@ const indexInfo = {
 	hideHeader: true,
 };
 const INFO = ref<PageMetadata>(indexInfo);
-const el = shallowRef<HTMLElement | null>(null);
+const el = useTemplateRef('el');
 const childInfo = ref<null | PageMetadata>(null);
 
 const router = useRouter();
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index f059050621..530b63b701 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -66,7 +66,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, computed } from 'vue';
+import { useTemplateRef, computed } from 'vue';
 import { notificationTypes } from '@@/js/const.js';
 import XNotificationConfig from './notifications.notification-config.vue';
 import type { NotificationConfig } from './notifications.notification-config.vue';
@@ -88,7 +88,7 @@ const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequest
 
 const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login', 'createToken'] satisfies (typeof notificationTypes[number])[] as string[];
 
-const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
+const allowButton = useTemplateRef('allowButton');
 const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
 const sendReadMessage = computed(() => pushRegistrationInServer.value?.sendReadMessage || false);
 const userLists = await misskeyApi('users/lists/list');
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 68f4b7a26d..38497e7c1b 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, provide, shallowRef, ref, onMounted, onActivated } from 'vue';
+import { computed, watch, provide, useTemplateRef, ref, onMounted, onActivated } from 'vue';
 import { scroll } from '@@/js/scroll.js';
 import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
 import type { MenuItem } from '@/types/menu.js';
@@ -59,8 +59,8 @@ import { prefer } from '@/preferences.js';
 
 provide('shouldOmitHeaderTitle', true);
 
-const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
-const rootEl = shallowRef<HTMLElement>();
+const tlComponent = useTemplateRef('tlComponent');
+const rootEl = useTemplateRef('rootEl');
 
 type TimelinePageSrc = BasicTimelineType | `list:${string}`;
 
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index e4857c7d30..be84fd6efe 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -24,10 +24,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, ref, shallowRef } from 'vue';
+import { computed, watch, ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkTimeline from '@/components/MkTimeline.vue';
 import { scroll } from '@@/js/scroll.js';
+import MkTimeline from '@/components/MkTimeline.vue';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { definePage } from '@/page.js';
 import { i18n } from '@/i18n.js';
@@ -41,8 +41,8 @@ const props = defineProps<{
 
 const list = ref<Misskey.entities.UserList | null>(null);
 const queue = ref(0);
-const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
-const rootEl = shallowRef<HTMLElement>();
+const tlEl = useTemplateRef('tlEl');
+const rootEl = useTemplateRef('rootEl');
 
 watch(() => props.listId, async () => {
 	list.value = await misskeyApi('users/lists/show', {
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index 3b5e9617d5..f5d2002669 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -14,11 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
-import type { ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
+import type { ChartDataset } from 'chart.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { store } from '@/store.js';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -33,8 +33,8 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = useTemplateRef('chartEl');
+const legendEl = useTemplateRef('legendEl');
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index af8e4d43a6..01c62810d4 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -14,11 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
-import type { ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
+import type { ChartDataset } from 'chart.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { store } from '@/store.js';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -33,8 +33,8 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = useTemplateRef('chartEl');
+const legendEl = useTemplateRef('legendEl');
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 50;
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 32e4e78d21..ed12b1b5c7 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -14,11 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, useTemplateRef, ref } from 'vue';
 import { Chart } from 'chart.js';
-import type { ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
+import type { ChartDataset } from 'chart.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 import { store } from '@/store.js';
 import { useChartTooltip } from '@/use/use-chart-tooltip.js';
@@ -33,8 +33,8 @@ const props = defineProps<{
 	user: Misskey.entities.User;
 }>();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const legendEl = shallowRef<InstanceType<typeof MkChartLegend>>();
+const chartEl = useTemplateRef('chartEl');
+const legendEl = useTemplateRef('legendEl');
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
diff --git a/packages/frontend/src/pages/welcome.timeline.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue
index 8fb84fd58f..680fe08c14 100644
--- a/packages/frontend/src/pages/welcome.timeline.note.vue
+++ b/packages/frontend/src/pages/welcome.timeline.note.vue
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, onUpdated, onMounted } from 'vue';
+import { ref, useTemplateRef, onUpdated, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
 import MkMediaList from '@/components/MkMediaList.vue';
@@ -45,7 +45,7 @@ defineProps<{
 	note: Misskey.entities.Note;
 }>();
 
-const noteTextEl = shallowRef<HTMLDivElement>();
+const noteTextEl = useTemplateRef('noteTextEl');
 const shouldCollapse = ref(false);
 const showContent = ref(false);
 
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 180b2e5f77..6108a8a698 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -21,15 +21,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import { onUpdated, ref, shallowRef } from 'vue';
+import { onUpdated, ref, useTemplateRef } from 'vue';
+import { getScrollContainer } from '@@/js/scroll.js';
 import XNote from '@/pages/welcome.timeline.note.vue';
 import { misskeyApiGet } from '@/utility/misskey-api.js';
-import { getScrollContainer } from '@@/js/scroll.js';
 
 const notes = ref<Misskey.entities.Note[]>([]);
 const isScrolling = ref(false);
 const scrollState = ref<null | 'intro' | 'loop'>(null);
-const notesMainContainerEl = shallowRef<HTMLElement>();
+const notesMainContainerEl = useTemplateRef('notesMainContainerEl');
 
 misskeyApiGet('notes/featured').then(_notes => {
 	notes.value = _notes;
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index 8200b6ad38..43d24da28b 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, computed, watch, ref, shallowRef } from 'vue';
+import { defineAsyncComponent, computed, watch, ref, useTemplateRef } from 'vue';
 import { openInstanceMenu } from './_common_/common.js';
 // import { host } from '@@/js/config.js';
 import * as os from '@/os.js';
@@ -76,7 +76,7 @@ const otherNavItemIndicated = computed<boolean>(() => {
 	}
 	return false;
 });
-const el = shallowRef<HTMLElement>();
+const el = useTemplateRef('el');
 // let accounts = $ref([]);
 // let connection = $ref(null);
 const iconOnly = ref(false);
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 2ef06726f9..c17e78bb03 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, onMounted, provide, ref, computed, shallowRef } from 'vue';
+import { defineAsyncComponent, onMounted, provide, ref, computed, useTemplateRef } from 'vue';
 import { instanceName } from '@@/js/config.js';
 import { isLink } from '@@/js/is-link.js';
 import XSidebar from './classic.sidebar.vue';
@@ -77,7 +77,7 @@ const fullView = ref(false);
 const globalHeaderHeight = ref(0);
 const wallpaper = miLocalStorage.getItem('wallpaper') != null;
 const showMenuOnTop = computed(() => store.s.menuDisplay === 'top');
-const live2d = shallowRef<HTMLIFrameElement>();
+const live2d = useTemplateRef('live2d');
 const widgetsLeft = ref<HTMLElement>();
 const widgetsRight = ref<HTMLElement>();
 
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 4e9ec7c586..4c33d7cb27 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, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, ref, useTemplateRef } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XCommon from './_common_/common.vue';
 import XSidebar from '@/ui/_common_/navbar.vue';
@@ -169,7 +169,7 @@ function showSettings() {
 	os.pageWindow('/settings/deck');
 }
 
-const columnsEl = shallowRef<HTMLElement>();
+const columnsEl = useTemplateRef('columnsEl');
 
 const addColumn = async (ev) => {
 	const { canceled, result: column } = await os.select({
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index ae282ba324..194b56c842 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, ref, shallowRef, watch, defineAsyncComponent } from 'vue';
+import { onMounted, ref, useTemplateRef, watch, defineAsyncComponent } from 'vue';
 import XColumn from './column.vue';
 import type { entities as MisskeyEntities } from 'misskey-js';
 import type { Column } from '@/deck.js';
@@ -34,7 +34,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
 const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
 const antennaName = ref<string | null>(null);
 
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 964b42874e..c2644da707 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, shallowRef, watch, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XColumn from './column.vue';
 import type { Column } from '@/deck.js';
@@ -40,7 +40,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
 const channel = shallowRef<Misskey.entities.Channel>();
 const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
 
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 3358b4e52b..4841a78323 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, shallowRef, ref, computed } from 'vue';
+import { onBeforeUnmount, onMounted, provide, watch, useTemplateRef, ref, computed } from 'vue';
 import type { Column } from '@/deck.js';
 import type { MenuItem } from '@/types/menu.js';
 import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from '@/deck.js';
@@ -68,7 +68,7 @@ const emit = defineEmits<{
 	(ev: 'headerWheel', ctx: WheelEvent): void;
 }>();
 
-const body = shallowRef<HTMLDivElement | null>();
+const body = useTemplateRef('body');
 
 const dragging = ref(false);
 watch(dragging, v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 2b4e86b8a2..a8f17feb23 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, shallowRef, ref, onMounted } from 'vue';
+import { watch, useTemplateRef, ref, onMounted } from 'vue';
 import XColumn from './column.vue';
 import type { entities as MisskeyEntities } from 'misskey-js';
 import type { Column } from '@/deck.js';
@@ -34,7 +34,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
 const withRenotes = ref(props.column.withRenotes ?? true);
 const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
 const listName = ref<string | null>(null);
diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue
index 0a2c0e9952..8378dddfef 100644
--- a/packages/frontend/src/ui/deck/notifications-column.vue
+++ b/packages/frontend/src/ui/deck/notifications-column.vue
@@ -12,10 +12,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, shallowRef } from 'vue';
+import { defineAsyncComponent, useTemplateRef } from 'vue';
 import XColumn from './column.vue';
-import { updateColumn } from '@/deck.js';
 import type { Column } from '@/deck.js';
+import { updateColumn } from '@/deck.js';
 import XNotifications from '@/components/MkNotifications.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
@@ -25,7 +25,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const notificationsComponent = shallowRef<InstanceType<typeof XNotifications>>();
+const notificationsComponent = useTemplateRef('notificationsComponent');
 
 function func() {
 	const { dispose } = 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 5d07440017..468b3e49e0 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 { computed, onMounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
 import XColumn from './column.vue';
 import type { Column } from '@/deck.js';
 import type { MenuItem } from '@/types/menu.js';
@@ -32,7 +32,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
 const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
 const roleName = ref<string | null>(null);
 
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index 2dec7bf5aa..6759135654 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, watch, ref, shallowRef, computed } from 'vue';
+import { onMounted, watch, ref, useTemplateRef, computed } from 'vue';
 import XColumn from './column.vue';
 import type { Column } from '@/deck.js';
 import type { MenuItem } from '@/types/menu.js';
@@ -50,7 +50,7 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const timeline = shallowRef<InstanceType<typeof MkTimeline>>();
+const timeline = useTemplateRef('timeline');
 
 const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 });
 const withRenotes = ref(props.column.withRenotes ?? true);
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 133360972b..d85745bde6 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -95,7 +95,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef } from 'vue';
+import { defineAsyncComponent, provide, onMounted, computed, ref, watch, useTemplateRef } from 'vue';
 import { instanceName } from '@@/js/config.js';
 import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js';
 import { isLink } from '@@/js/is-link.js';
@@ -135,7 +135,7 @@ window.addEventListener('resize', () => {
 
 const pageMetadata = ref<null | PageMetadata>(null);
 const widgetsShowing = ref(false);
-const navFooter = shallowRef<HTMLElement>();
+const navFooter = useTemplateRef('navFooter');
 
 provide(DI.router, mainRouter);
 provideMetadataReceiver((metadataGetter) => {
diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue
index 29e21ee6c3..2bc7facc88 100644
--- a/packages/frontend/src/widgets/WidgetAichan.vue
+++ b/packages/frontend/src/widgets/WidgetAichan.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, shallowRef } from 'vue';
+import { onMounted, onUnmounted, useTemplateRef } from 'vue';
 import { useWidgetPropsManager } from './widget.js';
 import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
 import type { GetFormResultType } from '@/utility/form.js';
@@ -35,7 +35,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const live2d = shallowRef<HTMLIFrameElement>();
+const live2d = useTemplateRef('live2d');
 
 const touched = () => {
 	//if (this.live2d) this.live2d.changeExpression('gurugurume');
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index c6724127fa..0c9f98f9e3 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <MkContainer :naked="widgetProps.transparent" :showHeader="false" class="mkw-instance-cloud">
 	<div class="">
-		<MkTagCloud v-if="activeInstances">
+		<MkTagCloud v-if="activeInstances" ref="cloud">
 			<li v-for="instance in activeInstances" :key="instance.id">
 				<a @click.prevent="onInstanceClick(instance)">
 					<img style="width: 32px;" :src="getInstanceIcon(instance)">
@@ -18,8 +18,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { shallowRef } from 'vue';
+import { shallowRef, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
+import { useInterval } from '@@/js/use-interval.js';
 import { useWidgetPropsManager } from './widget.js';
 import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import type { GetFormResultType } from '@/utility/form.js';
@@ -27,7 +28,6 @@ import MkContainer from '@/components/MkContainer.vue';
 import MkTagCloud from '@/components/MkTagCloud.vue';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
-import { useInterval } from '@@/js/use-interval.js';
 import { getProxiedImageUrlNullable } from '@/utility/media-proxy.js';
 
 const name = 'instanceCloud';
@@ -50,7 +50,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const cloud = shallowRef<InstanceType<typeof MkTagCloud> | null>();
+const cloud = useTemplateRef('cloud');
 const activeInstances = shallowRef<Misskey.entities.FederationInstance[] | null>(null);
 
 function onInstanceClick(i) {
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index 2cbf3a8ef6..2ccbb7a28f 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -17,14 +17,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, shallowRef } from 'vue';
+import { onMounted, ref, useTemplateRef } from 'vue';
 import * as Misskey from 'misskey-js';
+import { useInterval } from '@@/js/use-interval.js';
 import { useWidgetPropsManager } from './widget.js';
 import type { WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import type { GetFormResultType } from '@/utility/form.js';
 import * as os from '@/os.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
-import { useInterval } from '@@/js/use-interval.js';
 import { i18n } from '@/i18n.js';
 
 const name = 'slideshow';
@@ -54,8 +54,8 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
 
 const images = ref<Misskey.entities.DriveFile[]>([]);
 const fetching = ref(true);
-const slideA = shallowRef<HTMLElement>();
-const slideB = shallowRef<HTMLElement>();
+const slideA = useTemplateRef('slideA');
+const slideB = useTemplateRef('slideB');
 
 const change = () => {
 	if (images.value.length === 0) return;
-- 
cgit v1.2.3-freya

