From 21c1e086f9aaf2d1c200c50f589c7238e6892140 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 08:26:39 +0900
Subject: [PATCH 01/24] :art:

---
 packages/frontend/src/ui/universal.vue | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 865616f192..9f1324313e 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -287,7 +287,7 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
 		z-index: 1000;
 		bottom: 0;
 		left: 0;
-		padding: 12px 12px max(12px, calc(env(safe-area-inset-bottom, 0px) + 8px)) 12px;
+		padding: 12px 12px max(12px, env(safe-area-inset-bottom, 0px)) 12px;
 		display: grid;
 		grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
 		grid-gap: 8px;
@@ -333,6 +333,11 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
 					opacity: 0.5;
 				}
 			}
+
+			&.post {
+				background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+				color: var(--fgOnAccent);
+			}
 		}
 	}
 

From 771348e887058151ca35aead73ad4b9b625355a8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 08:29:25 +0900
Subject: [PATCH 02/24] =?UTF-8?q?PV=E9=9B=86=E8=A8=88=E3=81=AF=E3=83=AD?=
 =?UTF-8?q?=E3=83=BC=E3=82=AB=E3=83=AB=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?=
 =?UTF-8?q?=E3=81=AE=E3=81=BF=E3=81=AB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../backend/src/server/api/endpoints/users/show.ts     | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 48a6bbf9bc..fcdaeae1c9 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -139,10 +139,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 					throw new ApiError(meta.errors.noSuchUser);
 				}
 
-				if (me == null && ip != null) {
-					this.perUserPvChart.commitByVisitor(user, ip);
-				} else if (me && me.id !== user.id) {
-					this.perUserPvChart.commitByUser(user, me.id);
+				if (user.host == null) {
+					if (me == null && ip != null) {
+						this.perUserPvChart.commitByVisitor(user, ip);
+					} else if (me && me.id !== user.id) {
+						this.perUserPvChart.commitByUser(user, me.id);
+					}
 				}
 
 				return await this.userEntityService.pack(user, me, {

From 7093573f0ccc5bb4b674aca34757cdeb5dd74548 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 08:48:00 +0900
Subject: [PATCH 03/24] drop latestRequestSentAt of instance

---
 locales/ja-JP.yml                                  |  1 -
 .../1672703171386-remove-latestRequestSentAt.js    | 11 +++++++++++
 .../src/core/entities/InstanceEntityService.ts     |  3 +--
 packages/backend/src/models/entities/Instance.ts   |  8 --------
 .../src/models/schema/federation-instance.ts       |  5 -----
 .../queue/processors/DeliverProcessorService.ts    | 14 ++++++--------
 packages/frontend/src/pages/instance-info.vue      |  4 ----
 7 files changed, 18 insertions(+), 28 deletions(-)
 create mode 100644 packages/backend/migration/1672703171386-remove-latestRequestSentAt.js

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index eb30eed53a..d6a5518196 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -167,7 +167,6 @@ annotation: "注釈"
 federation: "連合"
 instances: "インスタンス"
 registeredAt: "初観測"
-latestRequestSentAt: "直近のリクエスト送信"
 latestRequestReceivedAt: "直近のリクエスト受信"
 latestStatus: "直近のステータス"
 storageUsage: "ストレージ使用量"
diff --git a/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js b/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js
new file mode 100644
index 0000000000..c9b28dd7e1
--- /dev/null
+++ b/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js
@@ -0,0 +1,11 @@
+export class removeLatestRequestSentAt1672703171386 {
+    name = 'removeLatestRequestSentAt1672703171386'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "latestRequestSentAt"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" ADD "latestRequestSentAt" TIMESTAMP WITH TIME ZONE`);
+    }
+}
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index 5a7ceb89a3..7742608b07 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -7,8 +7,8 @@ import type { } from '@/models/entities/Blocking.js';
 import type { User } from '@/models/entities/User.js';
 import type { Instance } from '@/models/entities/Instance.js';
 import { MetaService } from '@/core/MetaService.js';
-import { UserEntityService } from './UserEntityService.js';
 import { bindThis } from '@/decorators.js';
+import { UserEntityService } from './UserEntityService.js';
 
 @Injectable()
 export class InstanceEntityService {
@@ -33,7 +33,6 @@ export class InstanceEntityService {
 			notesCount: instance.notesCount,
 			followingCount: instance.followingCount,
 			followersCount: instance.followersCount,
-			latestRequestSentAt: instance.latestRequestSentAt ? instance.latestRequestSentAt.toISOString() : null,
 			lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(),
 			isNotResponding: instance.isNotResponding,
 			isSuspended: instance.isSuspended,
diff --git a/packages/backend/src/models/entities/Instance.ts b/packages/backend/src/models/entities/Instance.ts
index 7ea9234384..e4e9835ce8 100644
--- a/packages/backend/src/models/entities/Instance.ts
+++ b/packages/backend/src/models/entities/Instance.ts
@@ -59,14 +59,6 @@ export class Instance {
 	})
 	public followersCount: number;
 
-	/**
-	 * 直近のリクエスト送信日時
-	 */
-	@Column('timestamp with time zone', {
-		nullable: true,
-	})
-	public latestRequestSentAt: Date | null;
-
 	/**
 	 * 直近のリクエスト送信時のHTTPステータスコード
 	 */
diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts
index c57b3fec19..739548103a 100644
--- a/packages/backend/src/models/schema/federation-instance.ts
+++ b/packages/backend/src/models/schema/federation-instance.ts
@@ -32,11 +32,6 @@ export const packedFederationInstanceSchema = {
 			type: 'number',
 			optional: false, nullable: false,
 		},
-		latestRequestSentAt: {
-			type: 'string',
-			optional: false, nullable: true,
-			format: 'date-time',
-		},
 		lastCommunicatedAt: {
 			type: 'string',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 58969d550e..0e74260386 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -15,10 +15,10 @@ import ApRequestChart from '@/core/chart/charts/ap-request.js';
 import FederationChart from '@/core/chart/charts/federation.js';
 import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type Bull from 'bull';
 import type { DeliverJobData } from '../types.js';
-import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class DeliverProcessorService {
@@ -85,7 +85,6 @@ export class DeliverProcessorService {
 			// Update stats
 			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
 				this.instancesRepository.update(i.id, {
-					latestRequestSentAt: new Date(),
 					latestStatus: 200,
 					lastCommunicatedAt: new Date(),
 					isNotResponding: false,
@@ -100,10 +99,9 @@ export class DeliverProcessorService {
 
 			return 'Success';
 		} catch (res) {
-		// Update stats
+			// Update stats
 			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
 				this.instancesRepository.update(i.id, {
-					latestRequestSentAt: new Date(),
 					latestStatus: res instanceof StatusError ? res.statusCode : null,
 					isNotResponding: true,
 				});
@@ -114,17 +112,17 @@ export class DeliverProcessorService {
 			});
 
 			if (res instanceof StatusError) {
-			// 4xx
+				// 4xx
 				if (res.isClientError) {
-				// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
-				// 何回再送しても成功することはないということなのでエラーにはしないでおく
+					// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
+					// 何回再送しても成功することはないということなのでエラーにはしないでおく
 					return `${res.statusCode} ${res.statusMessage}`;
 				}
 
 				// 5xx etc.
 				throw `${res.statusCode} ${res.statusMessage}`;
 			} else {
-			// DNS error, socket error, timeout ...
+				// DNS error, socket error, timeout ...
 				throw res;
 			}
 		}
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index f750fdcee2..23705cb22d 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -40,10 +40,6 @@
 					<template #key>{{ i18n.ts.updatedAt }}</template>
 					<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
 				</MkKeyValue>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>{{ i18n.ts.latestRequestSentAt }}</template>
-					<template #value><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></template>
-				</MkKeyValue>
 				<MkKeyValue oneline style="margin: 1em 0;">
 					<template #key>{{ i18n.ts.latestStatus }}</template>
 					<template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template>

From c8bd13971355e1f2a720bdd7d5c75404833bc0bd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 09:00:42 +0900
Subject: [PATCH 04/24] drop lastCommunicatedAt of instance

---
 .../1672704017999-remove-lastCommunicatedAt.js        | 11 +++++++++++
 packages/backend/src/core/FederatedInstanceService.ts |  1 -
 packages/backend/src/core/chart/charts/federation.ts  |  4 ++--
 .../src/core/entities/InstanceEntityService.ts        |  1 -
 packages/backend/src/models/entities/Instance.ts      |  6 ------
 .../backend/src/models/schema/federation-instance.ts  |  5 -----
 .../src/queue/processors/DeliverProcessorService.ts   |  1 -
 .../src/queue/processors/InboxProcessorService.ts     |  1 -
 .../src/server/api/endpoints/federation/instances.ts  |  2 --
 packages/frontend/src/pages/about.federation.vue      |  4 +---
 .../frontend/src/pages/admin/overview.instances.vue   |  2 +-
 packages/frontend/src/pages/admin/overview.vue        |  2 +-
 .../frontend/src/ui/_common_/statusbar-federation.vue |  2 +-
 packages/frontend/src/widgets/federation.vue          |  2 +-
 packages/frontend/src/widgets/instance-cloud.vue      |  2 +-
 15 files changed, 19 insertions(+), 27 deletions(-)
 create mode 100644 packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js

diff --git a/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js b/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js
new file mode 100644
index 0000000000..38a6769851
--- /dev/null
+++ b/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js
@@ -0,0 +1,11 @@
+export class removeLastCommunicatedAt1672704017999 {
+    name = 'removeLastCommunicatedAt1672704017999'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "lastCommunicatedAt"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" ADD "lastCommunicatedAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
+    }
+}
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index 97745a1168..48e77caaee 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -35,7 +35,6 @@ export class FederatedInstanceService {
 				id: this.idService.genId(),
 				host,
 				caughtAt: new Date(),
-				lastCommunicatedAt: new Date(),
 			}).then(x => this.instancesRepository.findOneByOrFail(x.identifiers[0]));
 	
 			this.cache.set(host, i);
diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts
index d9234e8028..b8012809f7 100644
--- a/packages/backend/src/core/chart/charts/federation.ts
+++ b/packages/backend/src/core/chart/charts/federation.ts
@@ -86,7 +86,7 @@ export default class FederationChart extends Chart<typeof schema> {
 				.where(`instance.host IN (${ subInstancesQuery.getQuery() })`)
 				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts })
 				.andWhere('instance.isSuspended = false')
-				.andWhere('instance.lastCommunicatedAt > :gt', { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) })
+				.andWhere('instance.isNotResponding = false')
 				.getRawOne()
 				.then(x => parseInt(x.count, 10)),
 			this.instancesRepository.createQueryBuilder('instance')
@@ -94,7 +94,7 @@ export default class FederationChart extends Chart<typeof schema> {
 				.where(`instance.host IN (${ pubInstancesQuery.getQuery() })`)
 				.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocked)', { blocked: meta.blockedHosts })
 				.andWhere('instance.isSuspended = false')
-				.andWhere('instance.lastCommunicatedAt > :gt', { gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) })
+				.andWhere('instance.isNotResponding = false')
 				.getRawOne()
 				.then(x => parseInt(x.count, 10)),
 		]);
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index 7742608b07..81d02bb331 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -33,7 +33,6 @@ export class InstanceEntityService {
 			notesCount: instance.notesCount,
 			followingCount: instance.followingCount,
 			followersCount: instance.followersCount,
-			lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(),
 			isNotResponding: instance.isNotResponding,
 			isSuspended: instance.isSuspended,
 			isBlocked: meta.blockedHosts.includes(instance.host),
diff --git a/packages/backend/src/models/entities/Instance.ts b/packages/backend/src/models/entities/Instance.ts
index e4e9835ce8..40d964dece 100644
--- a/packages/backend/src/models/entities/Instance.ts
+++ b/packages/backend/src/models/entities/Instance.ts
@@ -75,12 +75,6 @@ export class Instance {
 	})
 	public latestRequestReceivedAt: Date | null;
 
-	/**
-	 * このインスタンスと最後にやり取りした日時
-	 */
-	@Column('timestamp with time zone')
-	public lastCommunicatedAt: Date;
-
 	/**
 	 * このインスタンスと不通かどうか
 	 */
diff --git a/packages/backend/src/models/schema/federation-instance.ts b/packages/backend/src/models/schema/federation-instance.ts
index 739548103a..f3f93f3097 100644
--- a/packages/backend/src/models/schema/federation-instance.ts
+++ b/packages/backend/src/models/schema/federation-instance.ts
@@ -32,11 +32,6 @@ export const packedFederationInstanceSchema = {
 			type: 'number',
 			optional: false, nullable: false,
 		},
-		lastCommunicatedAt: {
-			type: 'string',
-			optional: false, nullable: false,
-			format: 'date-time',
-		},
 		isNotResponding: {
 			type: 'boolean',
 			optional: false, nullable: false,
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 0e74260386..571b9e9045 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -86,7 +86,6 @@ export class DeliverProcessorService {
 			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
 				this.instancesRepository.update(i.id, {
 					latestStatus: 200,
-					lastCommunicatedAt: new Date(),
 					isNotResponding: false,
 				});
 
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index c032122caf..56058638c9 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -179,7 +179,6 @@ export class InboxProcessorService {
 		this.federatedInstanceService.registerOrFetchInstanceDoc(authUser.user.host).then(i => {
 			this.instancesRepository.update(i.id, {
 				latestRequestReceivedAt: new Date(),
-				lastCommunicatedAt: new Date(),
 				isNotResponding: false,
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 81276a7ab0..6672434fb5 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -64,8 +64,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				case '-followers': query.orderBy('instance.followersCount', 'ASC'); break;
 				case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break;
 				case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break;
-				case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break;
-				case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break;
 
 				default: query.orderBy('instance.id', 'DESC'); break;
 			}
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 2b20f383d1..1709f06a16 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -30,15 +30,13 @@
 				<option value="-followers">{{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }})</option>
 				<option value="+caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
 				<option value="-caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
-				<option value="+lastCommunicatedAt">{{ i18n.ts.lastCommunication }} ({{ i18n.ts.descendingOrder }})</option>
-				<option value="-lastCommunicatedAt">{{ i18n.ts.lastCommunication }} ({{ i18n.ts.ascendingOrder }})</option>
 			</MkSelect>
 		</FormSplit>
 	</div>
 
 	<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
 		<div class="dqokceoi">
-			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Last communicated: ${dateString(instance.lastCommunicatedAt)}\nStatus: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
 				<MkInstanceCardMini :instance="instance"/>
 			</MkA>
 		</div>
diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue
index ae10017aa8..15dbdc4639 100644
--- a/packages/frontend/src/pages/admin/overview.instances.vue
+++ b/packages/frontend/src/pages/admin/overview.instances.vue
@@ -22,7 +22,7 @@ const fetching = ref(true);
 
 const fetch = async () => {
 	const fetchedInstances = await os.api('federation/instances', {
-		sort: '+lastCommunicatedAt',
+		sort: '+latestRequestReceivedAt',
 		limit: 6,
 	});
 	instances.value = fetchedInstances;
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index d656e55200..5fdfa5f263 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -153,7 +153,7 @@ onMounted(async () => {
 	});
 
 	os.api('federation/instances', {
-		sort: '+lastCommunicatedAt',
+		sort: '+latestRequestReceivedAt',
 		limit: 25,
 	}).then(res => {
 		activeInstances = res;
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index 7c3de32ac9..70d683d755 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -44,7 +44,7 @@ let key = $ref(0);
 
 const tick = () => {
 	os.api('federation/instances', {
-		sort: '+lastCommunicatedAt',
+		sort: '+latestRequestReceivedAt',
 		limit: 30,
 	}).then(res => {
 		instances.value = res;
diff --git a/packages/frontend/src/widgets/federation.vue b/packages/frontend/src/widgets/federation.vue
index 221972bc6f..a701ca5673 100644
--- a/packages/frontend/src/widgets/federation.vue
+++ b/packages/frontend/src/widgets/federation.vue
@@ -58,7 +58,7 @@ const fetching = ref(true);
 
 const fetch = async () => {
 	const fetchedInstances = await os.api('federation/instances', {
-		sort: '+lastCommunicatedAt',
+		sort: '+latestRequestReceivedAt',
 		limit: 5,
 	});
 	const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.apiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
diff --git a/packages/frontend/src/widgets/instance-cloud.vue b/packages/frontend/src/widgets/instance-cloud.vue
index 4965616995..6d522ddbe8 100644
--- a/packages/frontend/src/widgets/instance-cloud.vue
+++ b/packages/frontend/src/widgets/instance-cloud.vue
@@ -54,7 +54,7 @@ function onInstanceClick(i) {
 
 useInterval(() => {
 	os.api('federation/instances', {
-		sort: '+lastCommunicatedAt',
+		sort: '+latestRequestReceivedAt',
 		limit: 25,
 	}).then(res => {
 		activeInstances = res;

From 4d0859ce7f33a02bc7242c8a9f22ce253ff9f7c0 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 09:03:04 +0900
Subject: [PATCH 05/24] drop latestStatus of instance

---
 .../migration/1672704136584-remove-latestStatus.js    | 11 +++++++++++
 packages/backend/src/models/entities/Instance.ts      |  8 --------
 .../src/queue/processors/DeliverProcessorService.ts   |  2 --
 packages/frontend/src/pages/instance-info.vue         |  4 ----
 4 files changed, 11 insertions(+), 14 deletions(-)
 create mode 100644 packages/backend/migration/1672704136584-remove-latestStatus.js

diff --git a/packages/backend/migration/1672704136584-remove-latestStatus.js b/packages/backend/migration/1672704136584-remove-latestStatus.js
new file mode 100644
index 0000000000..937c2fe8fd
--- /dev/null
+++ b/packages/backend/migration/1672704136584-remove-latestStatus.js
@@ -0,0 +1,11 @@
+export class removeLatestStatus1672704136584 {
+    name = 'removeLatestStatus1672704136584'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "latestStatus"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" ADD "latestStatus" integer`);
+    }
+}
diff --git a/packages/backend/src/models/entities/Instance.ts b/packages/backend/src/models/entities/Instance.ts
index 40d964dece..8092f67c86 100644
--- a/packages/backend/src/models/entities/Instance.ts
+++ b/packages/backend/src/models/entities/Instance.ts
@@ -59,14 +59,6 @@ export class Instance {
 	})
 	public followersCount: number;
 
-	/**
-	 * 直近のリクエスト送信時のHTTPステータスコード
-	 */
-	@Column('integer', {
-		nullable: true,
-	})
-	public latestStatus: number | null;
-
 	/**
 	 * 直近のリクエスト受信日時
 	 */
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 571b9e9045..fcff3a0e2a 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -85,7 +85,6 @@ export class DeliverProcessorService {
 			// Update stats
 			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
 				this.instancesRepository.update(i.id, {
-					latestStatus: 200,
 					isNotResponding: false,
 				});
 
@@ -101,7 +100,6 @@ export class DeliverProcessorService {
 			// Update stats
 			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
 				this.instancesRepository.update(i.id, {
-					latestStatus: res instanceof StatusError ? res.statusCode : null,
 					isNotResponding: true,
 				});
 
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 23705cb22d..55771b0e30 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -40,10 +40,6 @@
 					<template #key>{{ i18n.ts.updatedAt }}</template>
 					<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
 				</MkKeyValue>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>{{ i18n.ts.latestStatus }}</template>
-					<template #value>{{ instance.latestStatus ? instance.latestStatus : 'N/A' }}</template>
-				</MkKeyValue>
 				<MkKeyValue oneline style="margin: 1em 0;">
 					<template #key>{{ i18n.ts.latestRequestReceivedAt }}</template>
 					<template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>

From 631ffc8cf6efce264c84f08f2b1c54403a360942 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 09:19:54 +0900
Subject: [PATCH 06/24] fix style

---
 packages/frontend/src/pages/admin/index.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 406677229f..c71ebd4e2e 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -16,7 +16,7 @@
 			</div>
 		</MkSpacer>
 	</div>
-	<div v-if="!(narrow && currentPage?.route.name == null)" class="main" style="container-type: inline-size;">
+	<div v-if="!(narrow && currentPage?.route.name == null)" class="main">
 		<RouterView/>
 	</div>
 </div>

From 4cc71d24439375488655904048a3651455e44ad3 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 09:32:36 +0900
Subject: [PATCH 07/24] :v:

---
 .../src/core/FederatedInstanceService.ts      | 15 ++++++++++-
 .../backend/src/core/NoteCreateService.ts     |  2 +-
 .../backend/src/core/NoteDeleteService.ts     |  2 +-
 .../backend/src/core/UserFollowingService.ts  |  8 +++---
 .../activitypub/models/ApPersonService.ts     |  2 +-
 .../processors/DeliverProcessorService.ts     | 26 +++++++++++++------
 .../queue/processors/InboxProcessorService.ts |  5 +++-
 .../api/endpoints/federation/instances.ts     |  2 ++
 8 files changed, 45 insertions(+), 17 deletions(-)

diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index 48e77caaee..d517117da6 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -22,7 +22,7 @@ export class FederatedInstanceService {
 	}
 
 	@bindThis
-	public async registerOrFetchInstanceDoc(host: string): Promise<Instance> {
+	public async fetch(host: string): Promise<Instance> {
 		host = this.utilityService.toPuny(host);
 	
 		const cached = this.cache.get(host);
@@ -44,4 +44,17 @@ export class FederatedInstanceService {
 			return index;
 		}
 	}
+
+	@bindThis
+	public async updateCachePartial(host: string, data: Partial<Instance>): Promise<void> {
+		host = this.utilityService.toPuny(host);
+	
+		const cached = this.cache.get(host);
+		if (cached == null) return;
+	
+		this.cache.set(host, {
+			...cached,
+			...data,
+		});
+	}
 }
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index cc6c213446..6038840406 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -428,7 +428,7 @@ export class NoteCreateService {
 
 		// Register host
 		if (this.userEntityService.isRemoteUser(user)) {
-			this.federatedInstanceService.registerOrFetchInstanceDoc(user.host).then(i => {
+			this.federatedInstanceService.fetch(user.host).then(i => {
 				this.instancesRepository.increment({ id: i.id }, 'notesCount', 1);
 				this.instanceChart.updateNote(i.host, note, true);
 			});
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 1313fcbd3d..b1f16b6e8a 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -100,7 +100,7 @@ export class NoteDeleteService {
 			this.perUserNotesChart.update(user, note, false);
 
 			if (this.userEntityService.isRemoteUser(user)) {
-				this.federatedInstanceService.registerOrFetchInstanceDoc(user.host).then(i => {
+				this.federatedInstanceService.fetch(user.host).then(i => {
 					this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1);
 					this.instanceChart.updateNote(i.host, note, false);
 				});
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index 074aae86c8..52834c375e 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -205,12 +205,12 @@ export class UserFollowingService {
 	
 		//#region Update instance stats
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			this.federatedInstanceService.registerOrFetchInstanceDoc(follower.host).then(i => {
+			this.federatedInstanceService.fetch(follower.host).then(i => {
 				this.instancesRepository.increment({ id: i.id }, 'followingCount', 1);
 				this.instanceChart.updateFollowing(i.host, true);
 			});
 		} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			this.federatedInstanceService.registerOrFetchInstanceDoc(followee.host).then(i => {
+			this.federatedInstanceService.fetch(followee.host).then(i => {
 				this.instancesRepository.increment({ id: i.id }, 'followersCount', 1);
 				this.instanceChart.updateFollowers(i.host, true);
 			});
@@ -323,12 +323,12 @@ export class UserFollowingService {
 	
 		//#region Update instance stats
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			this.federatedInstanceService.registerOrFetchInstanceDoc(follower.host).then(i => {
+			this.federatedInstanceService.fetch(follower.host).then(i => {
 				this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1);
 				this.instanceChart.updateFollowing(i.host, false);
 			});
 		} else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
-			this.federatedInstanceService.registerOrFetchInstanceDoc(followee.host).then(i => {
+			this.federatedInstanceService.fetch(followee.host).then(i => {
 				this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1);
 				this.instanceChart.updateFollowers(i.host, false);
 			});
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index d5faf37df2..e08f33c906 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -348,7 +348,7 @@ export class ApPersonService implements OnModuleInit {
 		}
 
 		// Register host
-		this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
+		this.federatedInstanceService.fetch(host).then(i => {
 			this.instancesRepository.increment({ id: i.id }, 'usersCount', 1);
 			this.instanceChart.newUser(i.host);
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index fcff3a0e2a..d62e1f643a 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -83,10 +83,15 @@ export class DeliverProcessorService {
 			await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
 
 			// Update stats
-			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
-				this.instancesRepository.update(i.id, {
-					isNotResponding: false,
-				});
+			this.federatedInstanceService.fetch(host).then(i => {
+				if (i.isNotResponding) {
+					this.instancesRepository.update(i.id, {
+						isNotResponding: false,
+					});
+					this.federatedInstanceService.updateCachePartial(host, {
+						isNotResponding: false,
+					});
+				}
 
 				this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
 
@@ -98,10 +103,15 @@ export class DeliverProcessorService {
 			return 'Success';
 		} catch (res) {
 			// Update stats
-			this.federatedInstanceService.registerOrFetchInstanceDoc(host).then(i => {
-				this.instancesRepository.update(i.id, {
-					isNotResponding: true,
-				});
+			this.federatedInstanceService.fetch(host).then(i => {
+				if (!i.isNotResponding) {
+					this.instancesRepository.update(i.id, {
+						isNotResponding: true,
+					});
+					this.federatedInstanceService.updateCachePartial(host, {
+						isNotResponding: true,
+					});
+				}
 
 				this.instanceChart.requestSent(i.host, false);
 				this.apRequestChart.deliverFail();
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index 56058638c9..d033637849 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -176,11 +176,14 @@ export class InboxProcessorService {
 		}
 
 		// Update stats
-		this.federatedInstanceService.registerOrFetchInstanceDoc(authUser.user.host).then(i => {
+		this.federatedInstanceService.fetch(authUser.user.host).then(i => {
 			this.instancesRepository.update(i.id, {
 				latestRequestReceivedAt: new Date(),
 				isNotResponding: false,
 			});
+			this.federatedInstanceService.updateCachePartial(host, {
+				isNotResponding: false,
+			});
 
 			this.fetchInstanceMetadataService.fetchInstanceMetadata(i);
 
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 6672434fb5..180887285a 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -64,6 +64,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				case '-followers': query.orderBy('instance.followersCount', 'ASC'); break;
 				case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break;
 				case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break;
+				case '+latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'DESC'); break;
+				case '-latestRequestReceivedAt': query.orderBy('instance.latestRequestReceivedAt', 'ASC'); break;
 
 				default: query.orderBy('instance.id', 'DESC'); break;
 			}

From 06a37674094e3a095165d2a5015f5454d889e64b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 09:38:18 +0900
Subject: [PATCH 08/24] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 656f69643c..3b39c66042 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -41,6 +41,7 @@ You should also include the user name that made the change.
 - Server: delete outdated notifications regularly to improve db performance @syuilo
 - Server: delete outdated hard-mutes regularly to improve db performance @syuilo
 - Server: delete outdated notes of antenna regularly to improve db performance @syuilo
+- Server: improve activitypub deliver performance @syuilo
 - Client: use tabler-icons instead of fontawesome to better design @syuilo
 - Client: Add new gabber kick sounds (thanks for noizenecio)
 - Client: Add link to user RSS feed in profile menu @ssmucny

From a0af80f8c5bf91a393f2e3728dc4a85f9721f0bd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 09:41:32 +0900
Subject: [PATCH 09/24] tweak displayLimit of pagination

---
 packages/frontend/src/components/MkPagination.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index dc2a9832fe..b5a6bd271a 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -65,7 +65,7 @@ const props = withDefaults(defineProps<{
 	disableAutoLoad?: boolean;
 	displayLimit?: number;
 }>(), {
-	displayLimit: 30,
+	displayLimit: 20,
 });
 
 const emit = defineEmits<{

From 2184240ef146742a61ee12d5536922278b486d29 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 10:12:37 +0900
Subject: [PATCH 10/24] perf(client): use shallowRef for html element ref

---
 packages/frontend/src/components/MkAutocomplete.vue    | 10 +++++-----
 packages/frontend/src/components/MkButton.vue          |  4 ++--
 packages/frontend/src/components/MkCaptcha.vue         |  6 +++---
 packages/frontend/src/components/MkChart.vue           |  4 ++--
 packages/frontend/src/components/MkContextMenu.vue     |  2 +-
 packages/frontend/src/components/MkCropperDialog.vue   |  2 +-
 packages/frontend/src/components/MkDrive.vue           |  4 ++--
 packages/frontend/src/components/MkEmojiPicker.vue     |  6 +++---
 packages/frontend/src/components/MkHeatmap.vue         |  4 ++--
 packages/frontend/src/components/MkImgWithBlurhash.vue |  2 +-
 packages/frontend/src/components/MkInstanceStats.vue   |  4 ++--
 packages/frontend/src/components/MkMediaBanner.vue     |  2 +-
 packages/frontend/src/components/MkMenu.child.vue      |  4 ++--
 packages/frontend/src/components/MkMenu.vue            |  4 ++--
 packages/frontend/src/components/MkModal.vue           |  2 +-
 packages/frontend/src/components/MkModalWindow.vue     |  4 ++--
 packages/frontend/src/components/MkNote.vue            | 10 +++++-----
 packages/frontend/src/components/MkNoteDetailed.vue    | 10 +++++-----
 packages/frontend/src/components/MkNotification.vue    |  4 ++--
 packages/frontend/src/components/MkPagination.vue      |  4 ++--
 packages/frontend/src/components/MkPostForm.vue        |  8 ++++----
 .../src/components/MkReactionsViewer.reaction.vue      |  4 ++--
 packages/frontend/src/components/MkRenoteButton.vue    |  4 ++--
 .../frontend/src/components/MkRetentionHeatmap.vue     |  4 ++--
 packages/frontend/src/components/MkSparkle.vue         |  4 ++--
 packages/frontend/src/components/MkTagCloud.vue        |  6 +++---
 packages/frontend/src/components/MkTooltip.vue         |  4 ++--
 packages/frontend/src/components/MkVisibility.vue      |  2 +-
 packages/frontend/src/components/MkWindow.vue          |  2 +-
 packages/frontend/src/components/form/checkbox.vue     |  2 +-
 packages/frontend/src/components/form/folder.vue       |  2 +-
 packages/frontend/src/components/form/input.vue        |  8 ++++----
 packages/frontend/src/components/form/switch.vue       |  2 +-
 .../frontend/src/components/global/MkPageHeader.vue    |  4 ++--
 packages/frontend/src/components/global/MkSpacer.vue   |  5 ++---
 .../src/components/global/MkStickyContainer.vue        |  6 +++---
 packages/frontend/src/pages/about-misskey.vue          |  2 +-
 packages/frontend/src/pages/admin/_header_.vue         |  6 +++---
 .../frontend/src/pages/admin/overview.active-users.vue |  2 +-
 .../frontend/src/pages/admin/overview.ap-requests.vue  |  4 ++--
 packages/frontend/src/pages/admin/overview.pie.vue     |  4 ++--
 .../frontend/src/pages/admin/overview.queue.chart.vue  |  4 ++--
 packages/frontend/src/pages/admin/overview.vue         |  2 +-
 .../frontend/src/pages/admin/queue.chart.chart.vue     |  4 ++--
 packages/frontend/src/pages/antenna-timeline.vue       |  2 +-
 .../src/pages/messaging/messaging-room.form.vue        |  4 ++--
 .../frontend/src/pages/messaging/messaging-room.vue    |  2 +-
 packages/frontend/src/pages/settings/index.vue         |  4 ++--
 packages/frontend/src/pages/timeline.vue               |  2 +-
 packages/frontend/src/pages/user-list-timeline.vue     |  2 +-
 packages/frontend/src/pages/user/activity.heatmap.vue  |  4 ++--
 packages/frontend/src/pages/user/activity.pv.vue       |  2 +-
 packages/frontend/src/ui/classic.vue                   |  2 +-
 packages/frontend/src/ui/deck.vue                      |  2 +-
 packages/frontend/src/ui/deck/column.vue               |  2 +-
 packages/frontend/src/ui/universal.vue                 |  2 +-
 packages/frontend/src/ui/universal.widgets.vue         |  2 +-
 packages/frontend/src/widgets/aichan.vue               |  4 ++--
 packages/frontend/src/widgets/slideshow.vue            |  6 +++---
 59 files changed, 114 insertions(+), 115 deletions(-)

diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index a2b020b900..08e2c29de2 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -16,9 +16,9 @@
 		</li>
 	</ol>
 	<ol v-else-if="emojis.length > 0" ref="suggests" class="emojis">
-		<li v-for="emoji in emojis" tabindex="-1" :key="emoji.emoji" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
+		<li v-for="emoji in emojis" :key="emoji.emoji" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
 			<div class="emoji">
-				<MkEmoji :emoji="emoji.emoji" />
+				<MkEmoji :emoji="emoji.emoji"/>
 			</div>
 			<!-- eslint-disable-next-line vue/no-v-html -->
 			<span v-if="q" class="name" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
@@ -35,7 +35,8 @@
 </template>
 
 <script lang="ts">
-import { markRaw, ref, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
+import { markRaw, ref, shallowRef, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
+import sanitizeHtml from 'sanitize-html';
 import contains from '@/scripts/contains';
 import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base';
 import { acct } from '@/filters/user';
@@ -45,7 +46,6 @@ import { defaultStore } from '@/store';
 import { emojilist } from '@/scripts/emojilist';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
-import sanitizeHtml from 'sanitize-html';
 
 type EmojiDef = {
 	emoji: string;
@@ -136,7 +136,7 @@ const emit = defineEmits<{
 }>();
 
 const suggests = ref<Element>();
-const rootEl = ref<HTMLDivElement>();
+const rootEl = shallowRef<HTMLDivElement>();
 
 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 2b1b66da29..daf47e12d4 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -47,8 +47,8 @@ const emit = defineEmits<{
 	(ev: 'click', payload: MouseEvent): void;
 }>();
 
-let el = $ref<HTMLElement | null>(null);
-let ripples = $ref<HTMLElement | null>(null);
+let el = $shallowRef<HTMLElement | null>(null);
+let ripples = $shallowRef<HTMLElement | null>(null);
 
 onMounted(() => {
 	if (props.autofocus) {
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index 6d218389fc..8db2e54e88 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -6,7 +6,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue';
+import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
@@ -42,7 +42,7 @@ const emit = defineEmits<{
 
 const available = ref(false);
 
-const captchaEl = ref<HTMLDivElement | undefined>();
+const captchaEl = shallowRef<HTMLDivElement | undefined>();
 
 const variable = computed(() => {
 	switch (props.provider) {
@@ -62,7 +62,7 @@ const src = computed(() => {
 	}
 });
 
-const scriptId = computed(() => `script-${props.provider}`)
+const scriptId = computed(() => `script-${props.provider}`);
 
 const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
 
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index d602849651..7d3a3d289b 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -13,7 +13,7 @@
   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, watch, PropType, onUnmounted } from 'vue';
+import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue';
 import { Chart } from 'chart.js';
 import 'chartjs-adapter-date-fns';
 import { enUS } from 'date-fns/locale';
@@ -102,7 +102,7 @@ let chartData: {
 	}[];
 } = null;
 
-const chartEl = ref<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 const fetching = ref(true);
 
 const getDate = (ago: number) => {
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 6470f8b972..9d9abc5d09 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -22,7 +22,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let rootEl = $ref<HTMLDivElement>();
+let rootEl = $shallowRef<HTMLDivElement>();
 
 let zIndex = $ref<number>(os.claimZIndex('high'));
 
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index ae18160dea..3ff73d6669 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -51,7 +51,7 @@ const props = defineProps<{
 
 const imgUrl = getProxiedImageUrl(props.file.url);
 let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
-let imgEl = $ref<HTMLImageElement>();
+let imgEl = $shallowRef<HTMLImageElement>();
 let cropper: Cropper | null = null;
 let loading = $ref(true);
 
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index 4053870950..843c5f1c54 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -88,7 +88,7 @@
 </template>
 
 <script lang="ts" setup>
-import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from 'vue';
+import { markRaw, nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from './MkButton.vue';
 import XNavFolder from '@/components/MkDrive.navFolder.vue';
@@ -119,7 +119,7 @@ const emit = defineEmits<{
 }>();
 
 const loadMoreFiles = ref<InstanceType<typeof MkButton>>();
-const fileInput = ref<HTMLInputElement>();
+const fileInput = shallowRef<HTMLInputElement>();
 
 const folder = ref<Misskey.entities.DriveFolder | null>(null);
 const files = ref<Misskey.entities.DriveFile[]>([]);
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index fe098c9de6..4ce451c85c 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -77,7 +77,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, computed, watch, onMounted } from 'vue';
+import { ref, shallowRef, computed, watch, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import XSection from '@/components/MkEmojiPicker.section.vue';
 import { emojilist, UnicodeEmojiDef, unicodeEmojiCategories as categories } from '@/scripts/emojilist';
@@ -102,8 +102,8 @@ const emit = defineEmits<{
 	(ev: 'chosen', v: string): void;
 }>();
 
-const search = ref<HTMLInputElement>();
-const emojis = ref<HTMLDivElement>();
+const search = shallowRef<HTMLInputElement>();
+const emojis = shallowRef<HTMLDivElement>();
 
 const {
 	reactions: pinned,
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index f7a2db8509..fff93ef965 100644
--- a/packages/frontend/src/components/MkHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -27,8 +27,8 @@ const props = defineProps<{
 	src: string;
 }>();
 
-const rootEl = $ref<HTMLDivElement>(null);
-const chartEl = $ref<HTMLCanvasElement>(null);
+const rootEl = $shallowRef<HTMLDivElement>(null);
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
 let fetching = $ref(true);
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 80d7c201a4..6e651a06ab 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{
 	cover: true,
 });
 
-const canvas = $ref<HTMLCanvasElement>();
+const canvas = $shallowRef<HTMLCanvasElement>();
 let loaded = $ref(false);
 
 function draw() {
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index e576caf78a..531175b764 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -94,8 +94,8 @@ const chartLimit = 500;
 let chartSpan = $ref<'hour' | 'day'>('hour');
 let chartSrc = $ref('active-users');
 let heatmapSrc = $ref('active-users');
-let subDoughnutEl = $ref<HTMLCanvasElement>();
-let pubDoughnutEl = $ref<HTMLCanvasElement>();
+let subDoughnutEl = $shallowRef<HTMLCanvasElement>();
+let pubDoughnutEl = $shallowRef<HTMLCanvasElement>();
 
 const { handler: externalTooltipHandler1 } = useChartTooltip({
 	position: 'middle',
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index aa06c00fc6..718ce80e0d 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -38,7 +38,7 @@ const props = withDefaults(defineProps<{
 }>(), {
 });
 
-const audioEl = $ref<HTMLAudioElement | null>();
+const audioEl = $shallowRef<HTMLAudioElement | null>();
 let hide = $ref(true);
 
 function volumechange() {
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index c2705d394a..0ff8794c5d 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -6,7 +6,7 @@
 
 <script lang="ts" setup>
 import { on } from 'events';
-import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue';
+import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
 import MkMenu from './MkMenu.vue';
 import { MenuItem } from '@/types/menu';
 import * as os from '@/os';
@@ -24,7 +24,7 @@ const emit = defineEmits<{
 	(ev: 'actioned'): void;
 }>();
 
-const el = ref<HTMLElement>();
+const el = shallowRef<HTMLElement>();
 const align = 'left';
 
 function setPosition() {
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 64d18b6b7c..b893e80345 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -78,7 +78,7 @@ const emit = defineEmits<{
 	(ev: 'close', actioned?: boolean): void;
 }>();
 
-let itemsEl = $ref<HTMLDivElement>();
+let itemsEl = $shallowRef<HTMLDivElement>();
 
 let items2: InnerMenuItem[] = $ref([]);
 
@@ -112,7 +112,7 @@ watch(() => props.items, () => {
 });
 
 let childMenu = $ref<MenuItem[] | null>();
-let childTarget = $ref<HTMLElement | null>();
+let childTarget = $shallowRef<HTMLElement | null>();
 
 function closeChild() {
 	childMenu = null;
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index bd6ac02cc8..505b5e64bc 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -61,7 +61,7 @@ let maxHeight = $ref<number>();
 let fixed = $ref(false);
 let transformOrigin = $ref('center');
 let showing = $ref(true);
-let content = $ref<HTMLElement>();
+let content = $shallowRef<HTMLElement>();
 const zIndex = os.claimZIndex(props.zPriority);
 const type = $computed<ModalTypes>(() => {
 	if (props.preferType === 'auto') {
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index d977ca6e9c..bd7146ab88 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -42,8 +42,8 @@ const emit = defineEmits<{
 }>();
 
 let modal = $ref<InstanceType<typeof MkModal>>();
-let rootEl = $ref<HTMLElement>();
-let headerEl = $ref<HTMLElement>();
+let rootEl = $shallowRef<HTMLElement>();
+let headerEl = $shallowRef<HTMLElement>();
 let bodyWidth = $ref(0);
 let bodyHeight = $ref(0);
 
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index b379a8a7e4..4bbd44122d 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -101,7 +101,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, onUnmounted, reactive, ref, Ref } from 'vue';
+import { computed, inject, onMounted, onUnmounted, reactive, ref, shallowRef, Ref } from 'vue';
 import * as mfm from 'mfm-js';
 import * as misskey from 'misskey-js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
@@ -156,11 +156,11 @@ const isRenote = (
 	note.poll == null
 );
 
-const el = ref<HTMLElement>();
-const menuButton = ref<HTMLElement>();
+const el = shallowRef<HTMLElement>();
+const menuButton = shallowRef<HTMLElement>();
 const renoteButton = ref<InstanceType<typeof MkRenoteButton>>();
-const renoteTime = ref<HTMLElement>();
-const reactButton = ref<HTMLElement>();
+const renoteTime = shallowRef<HTMLElement>();
+const reactButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 79dff69be5..31188704ab 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -112,7 +112,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, onUnmounted, reactive, ref } from 'vue';
+import { computed, inject, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
 import * as mfm from 'mfm-js';
 import * as misskey from 'misskey-js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
@@ -166,11 +166,11 @@ const isRenote = (
 	note.poll == null
 );
 
-const el = ref<HTMLElement>();
-const menuButton = ref<HTMLElement>();
+const el = shallowRef<HTMLElement>();
+const menuButton = shallowRef<HTMLElement>();
 const renoteButton = ref<InstanceType<typeof MkRenoteButton>>();
-const renoteTime = ref<HTMLElement>();
-const reactButton = ref<HTMLElement>();
+const renoteTime = shallowRef<HTMLElement>();
+const reactButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
 const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index c8b197a850..a21a9e12a1 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -73,7 +73,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, onMounted, onUnmounted, watch } from 'vue';
+import { ref, shallowRef, onMounted, onUnmounted, watch } from 'vue';
 import * as misskey from 'misskey-js';
 import XReactionIcon from '@/components/MkReactionIcon.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
@@ -95,7 +95,7 @@ const props = withDefaults(defineProps<{
 	full: false,
 });
 
-const elRef = ref<HTMLElement>(null);
+const elRef = shallowRef<HTMLElement>(null);
 const reactionRef = ref(null);
 
 let readObserver: IntersectionObserver | undefined;
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index b5a6bd271a..2c0a30a888 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -32,7 +32,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, watch } from 'vue';
+import { computed, ComputedRef, isRef, markRaw, onActivated, onDeactivated, Ref, ref, shallowRef, watch } from 'vue';
 import * as misskey from 'misskey-js';
 import * as os from '@/os';
 import { onScrollTop, isTopVisible, getScrollPosition, getScrollContainer } from '@/scripts/scroll';
@@ -74,7 +74,7 @@ const emit = defineEmits<{
 
 type Item = { id: string; [another: string]: unknown; };
 
-const rootEl = ref<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
 const items = ref<Item[]>([]);
 const queue = ref<Item[]>([]);
 const offset = ref(0);
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index fb2cb5671d..883ad9f14f 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -128,10 +128,10 @@ const emit = defineEmits<{
 	(ev: 'esc'): void;
 }>();
 
-const textareaEl = $ref<HTMLTextAreaElement | null>(null);
-const cwInputEl = $ref<HTMLInputElement | null>(null);
-const hashtagsInputEl = $ref<HTMLInputElement | null>(null);
-const visibilityButton = $ref<HTMLElement | null>(null);
+const textareaEl = $shallowRef<HTMLTextAreaElement | null>(null);
+const cwInputEl = $shallowRef<HTMLInputElement | null>(null);
+const hashtagsInputEl = $shallowRef<HTMLInputElement | null>(null);
+const visibilityButton = $shallowRef<HTMLElement | null>(null);
 
 let posting = $ref(false);
 let posted = $ref(false);
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index b10c7009f5..e0e1262550 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -12,7 +12,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, ref, watch } from 'vue';
+import { computed, onMounted, ref, shallowRef, watch } from 'vue';
 import * as misskey from 'misskey-js';
 import XDetails from '@/components/MkReactionsViewer.details.vue';
 import XReactionIcon from '@/components/MkReactionIcon.vue';
@@ -28,7 +28,7 @@ const props = defineProps<{
 	note: misskey.entities.Note;
 }>();
 
-const buttonRef = ref<HTMLElement>();
+const buttonRef = shallowRef<HTMLElement>();
 
 const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
 
diff --git a/packages/frontend/src/components/MkRenoteButton.vue b/packages/frontend/src/components/MkRenoteButton.vue
index e0b1eaafc9..e84d4a3faa 100644
--- a/packages/frontend/src/components/MkRenoteButton.vue
+++ b/packages/frontend/src/components/MkRenoteButton.vue
@@ -14,7 +14,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, ref } from 'vue';
+import { computed, ref, shallowRef } from 'vue';
 import * as misskey from 'misskey-js';
 import XDetails from '@/components/MkUsersTooltip.vue';
 import { pleaseLogin } from '@/scripts/please-login';
@@ -28,7 +28,7 @@ const props = defineProps<{
 	count: number;
 }>();
 
-const buttonRef = ref<HTMLElement>();
+const buttonRef = shallowRef<HTMLElement>();
 
 const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
 
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 4dcf0cef9c..95690bde47 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -23,8 +23,8 @@ import { initChart } from '@/scripts/init-chart';
 
 initChart();
 
-const rootEl = $ref<HTMLDivElement>(null);
-const chartEl = $ref<HTMLCanvasElement>(null);
+const rootEl = $shallowRef<HTMLDivElement>(null);
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
 let fetching = $ref(true);
diff --git a/packages/frontend/src/components/MkSparkle.vue b/packages/frontend/src/components/MkSparkle.vue
index cdeaf9c417..0f268a9d1a 100644
--- a/packages/frontend/src/components/MkSparkle.vue
+++ b/packages/frontend/src/components/MkSparkle.vue
@@ -64,10 +64,10 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
 
 const particles = ref([]);
-const el = ref<HTMLElement>();
+const el = shallowRef<HTMLElement>();
 const width = ref(0);
 const height = ref(0);
 const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index 2dfd26edb0..9f7e76f18e 100644
--- a/packages/frontend/src/components/MkTagCloud.vue
+++ b/packages/frontend/src/components/MkTagCloud.vue
@@ -19,9 +19,9 @@ const computedStyle = getComputedStyle(document.documentElement);
 const idForCanvas = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
 const idForTags = Array.from(Array(16)).map(() => SAFE_FOR_HTML_ID[Math.floor(Math.random() * SAFE_FOR_HTML_ID.length)]).join('');
 let available = $ref(false);
-let rootEl = $ref<HTMLElement | null>(null);
-let canvasEl = $ref<HTMLCanvasElement | null>(null);
-let tagsEl = $ref<HTMLElement | null>(null);
+let rootEl = $shallowRef<HTMLElement | null>(null);
+let canvasEl = $shallowRef<HTMLCanvasElement | null>(null);
+let tagsEl = $shallowRef<HTMLElement | null>(null);
 let width = $ref(300);
 
 watch($$(available), () => {
diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue
index 9dba0c7350..399cec36c7 100644
--- a/packages/frontend/src/components/MkTooltip.vue
+++ b/packages/frontend/src/components/MkTooltip.vue
@@ -10,7 +10,7 @@
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, ref } from 'vue';
+import { nextTick, onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import * as os from '@/os';
 import { calcPopupPosition } from '@/scripts/popup-position';
 
@@ -34,7 +34,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const el = ref<HTMLElement>();
+const el = shallowRef<HTMLElement>();
 const zIndex = os.claimZIndex('high');
 
 function setPosition() {
diff --git a/packages/frontend/src/components/MkVisibility.vue b/packages/frontend/src/components/MkVisibility.vue
index 229907fbb8..2becb69d5a 100644
--- a/packages/frontend/src/components/MkVisibility.vue
+++ b/packages/frontend/src/components/MkVisibility.vue
@@ -22,7 +22,7 @@ const props = defineProps<{
 	},
 }>();
 
-const specified = $ref<HTMLElement>();
+const specified = $shallowRef<HTMLElement>();
 
 if (props.note.visibility === 'specified') {
 	useTooltip($$(specified), async (showing) => {
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index dca258421b..0f7e0e4f2e 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -88,7 +88,7 @@ const emit = defineEmits<{
 
 provide('inWindow', true);
 
-let rootEl = $ref<HTMLElement | null>();
+let rootEl = $shallowRef<HTMLElement | null>();
 let showing = $ref(true);
 let beforeClickedAt = 0;
 let maximized = $ref(false);
diff --git a/packages/frontend/src/components/form/checkbox.vue b/packages/frontend/src/components/form/checkbox.vue
index d869b600c9..a8e24dd839 100644
--- a/packages/frontend/src/components/form/checkbox.vue
+++ b/packages/frontend/src/components/form/checkbox.vue
@@ -35,7 +35,7 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', v: boolean): void;
 }>();
 
-let button = $ref<HTMLElement>();
+let button = $shallowRef<HTMLElement>();
 const checked = toRefs(props).modelValue;
 const toggle = () => {
 	if (props.disabled) return;
diff --git a/packages/frontend/src/components/form/folder.vue b/packages/frontend/src/components/form/folder.vue
index d7603e58d1..49d3bf93e1 100644
--- a/packages/frontend/src/components/form/folder.vue
+++ b/packages/frontend/src/components/form/folder.vue
@@ -40,7 +40,7 @@ const props = withDefaults(defineProps<{
 
 let opened = $ref(props.defaultOpen);
 let openedAtLeastOnce = $ref(props.defaultOpen);
-let root = $ref<HTMLElement>();
+let root = $shallowRef<HTMLElement>();
 
 function enter(el) {
 	const elementHeight = el.getBoundingClientRect().height;
diff --git a/packages/frontend/src/components/form/input.vue b/packages/frontend/src/components/form/input.vue
index b7e216bfe2..4f3e50c31a 100644
--- a/packages/frontend/src/components/form/input.vue
+++ b/packages/frontend/src/components/form/input.vue
@@ -34,7 +34,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, nextTick, ref, watch, computed, toRefs } from 'vue';
+import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue';
 import { debounce } from 'throttle-debounce';
 import MkButton from '@/components/MkButton.vue';
 import { useInterval } from '@/scripts/use-interval';
@@ -74,9 +74,9 @@ const focused = ref(false);
 const changed = ref(false);
 const invalid = ref(false);
 const filled = computed(() => v.value !== '' && v.value != null);
-const inputEl = ref<HTMLElement>();
-const prefixEl = ref<HTMLElement>();
-const suffixEl = ref<HTMLElement>();
+const inputEl = shallowRef<HTMLElement>();
+const prefixEl = shallowRef<HTMLElement>();
+const suffixEl = shallowRef<HTMLElement>();
 const height =
 	props.small ? 35 :
 	props.large ? 39 :
diff --git a/packages/frontend/src/components/form/switch.vue b/packages/frontend/src/components/form/switch.vue
index 1ed00ae655..5c9e3a5223 100644
--- a/packages/frontend/src/components/form/switch.vue
+++ b/packages/frontend/src/components/form/switch.vue
@@ -34,7 +34,7 @@ const emit = defineEmits<{
 	(ev: 'update:modelValue', v: boolean): void;
 }>();
 
-let button = $ref<HTMLElement>();
+let button = $shallowRef<HTMLElement>();
 const checked = toRefs(props).modelValue;
 const toggle = () => {
 	if (props.disabled) return;
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index 9c7c49ac58..e39cc70185 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -77,9 +77,9 @@ const metadata = injectPageMetadata();
 const hideTitle = inject('shouldOmitHeaderTitle', false);
 const thin_ = props.thin || inject('shouldHeaderThin', false);
 
-const el = $ref<HTMLElement | undefined>(undefined);
+const el = $shallowRef<HTMLElement | undefined>(undefined);
 const tabRefs: Record<string, HTMLElement | null> = {};
-const tabHighlightEl = $ref<HTMLElement | null>(null);
+const tabHighlightEl = $shallowRef<HTMLElement | null>(null);
 const bg = ref<string | undefined>(undefined);
 let narrow = $ref(false);
 const hasTabs = $computed(() => props.tabs.length > 0);
diff --git a/packages/frontend/src/components/global/MkSpacer.vue b/packages/frontend/src/components/global/MkSpacer.vue
index 01e7409801..88c1daaf23 100644
--- a/packages/frontend/src/components/global/MkSpacer.vue
+++ b/packages/frontend/src/components/global/MkSpacer.vue
@@ -24,8 +24,8 @@ const props = withDefaults(defineProps<{
 });
 
 let ro: ResizeObserver;
-let root = $ref<HTMLElement>();
-let content = $ref<HTMLElement>();
+let root = $shallowRef<HTMLElement>();
+let content = $shallowRef<HTMLElement>();
 let margin = $ref(props.marginMin);
 const widthHistory = [null, null] as [number | null, number | null];
 const heightHistory = [null, null] as [number | null, number | null];
@@ -72,7 +72,6 @@ onMounted(() => {
 		const pastHeight = heightHistory.pop();
 		heightHistory.unshift(height);
 
-
 		if (pastWidth === width && pastHeight === height) {
 			return;
 		}
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 44f4f065a6..a3fee91a36 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -18,9 +18,9 @@ const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
 <script lang="ts" setup>
 import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue';
 
-const rootEl = $ref<HTMLElement>();
-const headerEl = $ref<HTMLElement>();
-const bodyEl = $ref<HTMLElement>();
+const rootEl = $shallowRef<HTMLElement>();
+const headerEl = $shallowRef<HTMLElement>();
+const bodyEl = $shallowRef<HTMLElement>();
 
 let headerHeight = $ref<string | undefined>();
 let childStickyTop = $ref(0);
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 67f141d458..5085b12527 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -156,7 +156,7 @@ const patrons = [
 let easterEggReady = false;
 let easterEggEmojis = $ref([]);
 let easterEggEngine = $ref(null);
-const containerEl = $ref<HTMLElement>();
+const containerEl = $shallowRef<HTMLElement>();
 
 function iconLoaded() {
 	const emojis = defaultStore.state.reactions;
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index bdb41b2d2c..a342644516 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -28,7 +28,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, ref, inject, watch, nextTick } from 'vue';
+import { computed, onMounted, onUnmounted, ref, shallowRef, inject, watch, nextTick } from 'vue';
 import tinycolor from 'tinycolor2';
 import { popupMenu } from '@/os';
 import { url } from '@/config';
@@ -64,9 +64,9 @@ const emit = defineEmits<{
 
 const metadata = injectPageMetadata();
 
-const el = ref<HTMLElement>(null);
+const el = shallowRef<HTMLElement>(null);
 const tabRefs = {};
-const tabHighlightEl = $ref<HTMLElement | null>(null);
+const tabHighlightEl = $shallowRef<HTMLElement | null>(null);
 const bg = ref(null);
 const height = ref(0);
 const hasTabs = computed(() => {
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index d760d90ef1..e01009b7aa 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -23,7 +23,7 @@ import { initChart } from '@/scripts/init-chart';
 
 initChart();
 
-const chartEl = $ref<HTMLCanvasElement>(null);
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
 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 5dcb67474f..61a0667080 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -34,8 +34,8 @@ import { initChart } from '@/scripts/init-chart';
 initChart();
 
 const chartLimit = 50;
-const chartEl = $ref<HTMLCanvasElement>();
-const chartEl2 = $ref<HTMLCanvasElement>();
+const chartEl = $shallowRef<HTMLCanvasElement>();
+const chartEl2 = $shallowRef<HTMLCanvasElement>();
 let 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 b6f0d1b705..416e963356 100644
--- a/packages/frontend/src/pages/admin/overview.pie.vue
+++ b/packages/frontend/src/pages/admin/overview.pie.vue
@@ -3,7 +3,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import { Chart } from 'chart.js';
 import number from '@/filters/number';
 import { defaultStore } from '@/store';
@@ -16,7 +16,7 @@ const props = defineProps<{
 	data: { name: string; value: number; color: string; onClick?: () => void }[];
 }>();
 
-const chartEl = ref<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 
 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 1765577d45..0162d53665 100644
--- a/packages/frontend/src/pages/admin/overview.queue.chart.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue
@@ -3,7 +3,7 @@
 </template>
 
 <script lang="ts" setup>
-import { watch, onMounted, onUnmounted, ref } from 'vue';
+import { watch, onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import { Chart } from 'chart.js';
 import number from '@/filters/number';
 import * as os from '@/os';
@@ -19,7 +19,7 @@ const props = defineProps<{
 	type: string;
 }>();
 
-const chartEl = ref<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 5fdfa5f263..c16a928a80 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -82,7 +82,7 @@ import { defaultStore } from '@/store';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
 import MkFolder from '@/components/MkFolder.vue';
 
-const rootEl = $ref<HTMLElement>();
+const rootEl = $shallowRef<HTMLElement>();
 let serverInfo: any = $ref(null);
 let topSubInstancesForPie: any = $ref(null);
 let topPubInstancesForPie: any = $ref(null);
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue
index ae8d9ae4de..a0c05df983 100644
--- a/packages/frontend/src/pages/admin/queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue
@@ -3,7 +3,7 @@
 </template>
 
 <script lang="ts" setup>
-import { watch, onMounted, onUnmounted, ref } from 'vue';
+import { watch, onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import { Chart } from 'chart.js';
 import number from '@/filters/number';
 import * as os from '@/os';
@@ -19,7 +19,7 @@ const props = defineProps<{
 	type: string;
 }>();
 
-const chartEl = ref<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement>(null);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 1d5c3aa1d3..fd98ef5604 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -34,7 +34,7 @@ const props = defineProps<{
 
 let antenna = $ref(null);
 let queue = $ref(0);
-let rootEl = $ref<HTMLElement>();
+let rootEl = $shallowRef<HTMLElement>();
 let tlEl = $ref<InstanceType<typeof XTimeline>>();
 const keymap = $computed(() => ({
 	't': focus,
diff --git a/packages/frontend/src/pages/messaging/messaging-room.form.vue b/packages/frontend/src/pages/messaging/messaging-room.form.vue
index 84572815c0..2c54c6f71f 100644
--- a/packages/frontend/src/pages/messaging/messaging-room.form.vue
+++ b/packages/frontend/src/pages/messaging/messaging-room.form.vue
@@ -46,8 +46,8 @@ const props = defineProps<{
 	group?: Misskey.entities.UserGroup | null;
 }>();
 
-let textEl = $ref<HTMLTextAreaElement>();
-let fileEl = $ref<HTMLInputElement>();
+let textEl = $shallowRef<HTMLTextAreaElement>();
+let fileEl = $shallowRef<HTMLInputElement>();
 
 let text = $ref<string>('');
 let file = $ref<Misskey.entities.DriveFile | null>(null);
diff --git a/packages/frontend/src/pages/messaging/messaging-room.vue b/packages/frontend/src/pages/messaging/messaging-room.vue
index 280e6a903b..0db431b083 100644
--- a/packages/frontend/src/pages/messaging/messaging-room.vue
+++ b/packages/frontend/src/pages/messaging/messaging-room.vue
@@ -71,7 +71,7 @@ const props = defineProps<{
 	groupId?: string;
 }>();
 
-let rootEl = $ref<HTMLDivElement>();
+let rootEl = $shallowRef<HTMLDivElement>();
 let formEl = $ref<InstanceType<typeof XForm>>();
 let pagingComponent = $ref<InstanceType<typeof MkPagination>>();
 
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 9517e3a5c8..119a75b650 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -22,7 +22,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, defineAsyncComponent, inject, nextTick, onActivated, onMounted, onUnmounted, provide, ref, watch } from 'vue';
+import { computed, defineAsyncComponent, inject, nextTick, onActivated, onMounted, onUnmounted, provide, ref, shallowRef, watch } from 'vue';
 import { i18n } from '@/i18n';
 import MkInfo from '@/components/MkInfo.vue';
 import MkSuperMenu from '@/components/MkSuperMenu.vue';
@@ -40,7 +40,7 @@ const indexInfo = {
 	hideHeader: true,
 };
 const INFO = ref(indexInfo);
-const el = ref<HTMLElement | null>(null);
+const el = shallowRef<HTMLElement | null>(null);
 const childInfo = ref(null);
 
 const router = useRouter();
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index fbbe43f2c0..640d940d91 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -42,7 +42,7 @@ const keymap = {
 };
 
 const tlComponent = $ref<InstanceType<typeof XTimeline>>();
-const rootEl = $ref<HTMLElement>();
+const rootEl = $shallowRef<HTMLElement>();
 
 let queue = $ref(0);
 const src = $computed({ get: () => defaultStore.reactiveState.tl.value.src, set: (x) => saveSrc(x) });
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index a223dee319..b69bbd575f 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -35,7 +35,7 @@ const props = defineProps<{
 let list = $ref(null);
 let queue = $ref(0);
 let tlEl = $ref<InstanceType<typeof XTimeline>>();
-let rootEl = $ref<HTMLElement>();
+let rootEl = $shallowRef<HTMLElement>();
 
 watch(() => props.listId, async () => {
 	list = await os.api('users/lists/show', {
diff --git a/packages/frontend/src/pages/user/activity.heatmap.vue b/packages/frontend/src/pages/user/activity.heatmap.vue
index 000653ea5c..8472f79948 100644
--- a/packages/frontend/src/pages/user/activity.heatmap.vue
+++ b/packages/frontend/src/pages/user/activity.heatmap.vue
@@ -28,8 +28,8 @@ const props = defineProps<{
 	user: misskey.entities.User;
 }>();
 
-const rootEl = $ref<HTMLDivElement>(null);
-const chartEl = $ref<HTMLCanvasElement>(null);
+const rootEl = $shallowRef<HTMLDivElement>(null);
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
 let fetching = $ref(true);
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 25d708e71a..eaaeba976c 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -28,7 +28,7 @@ const props = defineProps<{
 	user: misskey.entities.User;
 }>();
 
-const chartEl = $ref<HTMLCanvasElement>(null);
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 6369bb8976..ba9120a77d 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -64,7 +64,7 @@ let fullView = $ref(false);
 let globalHeaderHeight = $ref(0);
 const wallpaper = localStorage.getItem('wallpaper') != null;
 const showMenuOnTop = $computed(() => defaultStore.state.menuDisplay === 'top');
-let live2d = $ref<HTMLIFrameElement>();
+let live2d = $shallowRef<HTMLIFrameElement>();
 let widgetsLeft = $ref();
 let widgetsRight = $ref();
 
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 6efb2ce290..b0306ab832 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -125,7 +125,7 @@ function showSettings() {
 	os.pageWindow('/settings/deck');
 }
 
-let columnsEl = $ref<HTMLElement>();
+let columnsEl = $shallowRef<HTMLElement>();
 
 const addColumn = async (ev) => {
 	const columns = [
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 5de5528b1d..0b8d6afe55 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -59,7 +59,7 @@ const emit = defineEmits<{
 	(ev: 'change-active-state', v: boolean): void;
 }>();
 
-let body = $ref<HTMLDivElement>();
+let body = $shallowRef<HTMLDivElement>();
 
 let dragging = $ref(false);
 watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 9f1324313e..9e1fee5b6b 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -86,7 +86,7 @@ window.addEventListener('resize', () => {
 });
 
 let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-const widgetsEl = $ref<HTMLElement>();
+const widgetsEl = $shallowRef<HTMLElement>();
 const widgetsShowing = $ref(false);
 
 provide('router', mainRouter);
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index 35de23ebfa..d4210f6988 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -31,7 +31,7 @@ const emit = defineEmits<{
 	(ev: 'mounted', el?: Element): void;
 }>();
 
-let rootEl = $ref<HTMLDivElement>();
+let rootEl = $shallowRef<HTMLDivElement>();
 
 const widgets = $computed(() => {
 	if (props.place === null) return defaultStore.reactiveState.widgets.value;
diff --git a/packages/frontend/src/widgets/aichan.vue b/packages/frontend/src/widgets/aichan.vue
index 828490fd9c..ab5b375ae4 100644
--- a/packages/frontend/src/widgets/aichan.vue
+++ b/packages/frontend/src/widgets/aichan.vue
@@ -5,7 +5,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, onUnmounted, reactive, ref } from 'vue';
+import { onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 
@@ -32,7 +32,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const live2d = ref<HTMLIFrameElement>();
+const live2d = shallowRef<HTMLIFrameElement>();
 
 const touched = () => {
 	//if (this.live2d) this.live2d.changeExpression('gurugurume');
diff --git a/packages/frontend/src/widgets/slideshow.vue b/packages/frontend/src/widgets/slideshow.vue
index e317b8ab94..2c937d85c5 100644
--- a/packages/frontend/src/widgets/slideshow.vue
+++ b/packages/frontend/src/widgets/slideshow.vue
@@ -12,7 +12,7 @@
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, onUnmounted, reactive, ref } from 'vue';
+import { nextTick, onMounted, onUnmounted, reactive, ref, shallowRef } from 'vue';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
@@ -49,8 +49,8 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
 
 const images = ref([]);
 const fetching = ref(true);
-const slideA = ref<HTMLElement>();
-const slideB = ref<HTMLElement>();
+const slideA = shallowRef<HTMLElement>();
+const slideB = shallowRef<HTMLElement>();
 
 const change = () => {
 	if (images.value.length === 0) return;

From 6c10588e77a0b328e33330e39f34756a3157ac7b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 10:46:56 +0900
Subject: [PATCH 11/24] refactor(client): refactor and performance improve of
 MkSpacer

---
 .../frontend/src/components/form/folder.vue   |  6 +-
 .../src/components/global/MkSpacer.vue        | 93 ++++---------------
 packages/frontend/src/ui/classic.vue          |  2 +-
 packages/frontend/src/ui/deck/column.vue      |  2 +-
 4 files changed, 24 insertions(+), 79 deletions(-)

diff --git a/packages/frontend/src/components/form/folder.vue b/packages/frontend/src/components/form/folder.vue
index 49d3bf93e1..40bbc97002 100644
--- a/packages/frontend/src/components/form/folder.vue
+++ b/packages/frontend/src/components/form/folder.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="dwzlatin" :class="{ opened }" ref="root">
+<div class="dwzlatin" :class="{ opened }">
 	<div class="header _button" @click="toggle">
 		<span class="icon"><slot name="icon"></slot></span>
 		<span class="text"><slot name="label"></slot></span>
@@ -19,7 +19,7 @@
 		>
 			<KeepAlive>
 				<div v-show="opened">
-					<MkSpacer :margin-min="14" :margin-max="22" :container="root">
+					<MkSpacer :margin-min="14" :margin-max="22">
 						<slot></slot>
 					</MkSpacer>
 				</div>
@@ -40,7 +40,6 @@ const props = withDefaults(defineProps<{
 
 let opened = $ref(props.defaultOpen);
 let openedAtLeastOnce = $ref(props.defaultOpen);
-let root = $shallowRef<HTMLElement>();
 
 function enter(el) {
 	const elementHeight = el.getBoundingClientRect().height;
@@ -142,6 +141,7 @@ function toggle() {
 	> .body {
 		background: var(--panel);
 		border-radius: 0 0 6px 6px;
+		container-type: inline-size;
 	}
 
 	&.opened {
diff --git a/packages/frontend/src/components/global/MkSpacer.vue b/packages/frontend/src/components/global/MkSpacer.vue
index 88c1daaf23..1ddb230bd6 100644
--- a/packages/frontend/src/components/global/MkSpacer.vue
+++ b/packages/frontend/src/components/global/MkSpacer.vue
@@ -1,6 +1,6 @@
 <template>
-<div ref="root" :class="$style.root" :style="{ padding: margin + 'px' }">
-	<div ref="content" :class="$style.content">
+<div :class="[$style.root, { [$style.rootMin]: forceSpacerMin }]">
+	<div :class="$style.content">
 		<slot></slot>
 	</div>
 </div>
@@ -14,84 +14,13 @@ const props = withDefaults(defineProps<{
 	contentMax?: number | null;
 	marginMin?: number;
 	marginMax?: number;
-
-	// MkFolderとかで開閉アニメーションの際にheightを正しく伝えるため
-	container?: HTMLElement,
 }>(), {
 	contentMax: null,
 	marginMin: 12,
 	marginMax: 24,
 });
 
-let ro: ResizeObserver;
-let root = $shallowRef<HTMLElement>();
-let content = $shallowRef<HTMLElement>();
-let margin = $ref(props.marginMin);
-const widthHistory = [null, null] as [number | null, number | null];
-const heightHistory = [null, null] as [number | null, number | null];
-const shouldSpacerMin = inject('shouldSpacerMin', false);
-
-const adjust = (rect: { width: number; height: number; }) => {
-	if (shouldSpacerMin || deviceKind === 'smartphone') {
-		margin = props.marginMin;
-		return;
-	}
-
-	if (rect.width > (props.contentMax ?? 0) || (rect.width > 360 && window.innerWidth > 400)) {
-		margin = props.marginMax;
-	} else {
-		margin = props.marginMin;
-	}
-};
-
-if (props.container) {
-	const width = props.container.offsetWidth;
-	const height = props.container.offsetHeight;
-	adjust({
-		width,
-		height,
-	});
-}
-
-onMounted(() => {
-	ro = new ResizeObserver((entries) => {
-		/* iOSが対応していない
-		adjust({
-			width: entries[0].borderBoxSize[0].inlineSize,
-			height: entries[0].borderBoxSize[0].blockSize,
-		});
-		*/
-
-		const width = props.container ? props.container.offsetWidth : root!.offsetWidth;
-		const height = props.container ? props.container.offsetHeight : root!.offsetHeight;
-
-		//#region Prevent infinite resizing
-		// https://github.com/misskey-dev/misskey/issues/9076
-		const pastWidth = widthHistory.pop();
-		widthHistory.unshift(width);
-		const pastHeight = heightHistory.pop();
-		heightHistory.unshift(height);
-
-		if (pastWidth === width && pastHeight === height) {
-			return;
-		}
-		//#endregion
-
-		adjust({
-			width,
-			height,
-		});
-	});
-	ro.observe(root!);
-
-	if (props.contentMax) {
-		content!.style.maxWidth = `${props.contentMax}px`;
-	}
-});
-
-onUnmounted(() => {
-	ro.disconnect();
-});
+const forceSpacerMin = inject('forceSpacerMin', false) || deviceKind === 'smartphone';
 </script>
 
 <style lang="scss" module>
@@ -99,9 +28,25 @@ onUnmounted(() => {
 	box-sizing: border-box;
 	width: 100%;
 }
+.rootMin {
+	padding: v-bind('props.marginMin + "px"') !important;
+}
 
 .content {
 	margin: 0 auto;
+	max-width: v-bind('props.contentMax + "px"');
 	container-type: inline-size;
 }
+
+@container (max-width: 360px) {
+	.root {
+		padding: v-bind('props.marginMin + "px"');
+	}
+}
+
+@container (min-width: 361px) {
+	.root {
+		padding: v-bind('props.marginMax + "px"');
+	}
+}
 </style>
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index ba9120a77d..280e69e7dd 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -76,7 +76,7 @@ provideMetadataReceiver((info) => {
 	}
 });
 provide('shouldHeaderThin', showMenuOnTop);
-provide('shouldSpacerMin', true);
+provide('forceSpacerMin', true);
 
 function attachSticky(el) {
 	const sticky = new StickySidebar(el, defaultStore.state.menuDisplay === 'top' ? 0 : 16, defaultStore.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 0b8d6afe55..775bdf6c1e 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -40,7 +40,7 @@ import { MenuItem } from '@/types/menu';
 
 provide('shouldHeaderThin', true);
 provide('shouldOmitHeaderTitle', true);
-provide('shouldSpacerMin', true);
+provide('forceSpacerMin', true);
 
 const props = withDefaults(defineProps<{
 	column: Column;

From aca445c7c824def25437e7d3d9c75adb60c30280 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 10:53:20 +0900
Subject: [PATCH 12/24] 13.0.0-beta.18

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 0ae0bb2a32..ec1fa7ef43 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.17",
+	"version": "13.0.0-beta.18",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From aa0eaf37b6e54719616548a971169ce8a6a2c17c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 10:57:52 +0900
Subject: [PATCH 13/24] :art:

---
 packages/frontend/src/components/MkMenu.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index b893e80345..0db0767fac 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -203,7 +203,7 @@ onBeforeUnmount(() => {
 	> .item {
 		display: block;
 		position: relative;
-		padding: 6px 16px;
+		padding: 5px 16px;
 		width: 100%;
 		box-sizing: border-box;
 		white-space: nowrap;

From 459151c1f210ba0df5ea0cd56d33f8551b75d91d Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 13:04:58 +0900
Subject: [PATCH 14/24] clean up

---
 packages/frontend/src/pages/admin/metrics.vue | 439 ------------------
 1 file changed, 439 deletions(-)
 delete mode 100644 packages/frontend/src/pages/admin/metrics.vue

diff --git a/packages/frontend/src/pages/admin/metrics.vue b/packages/frontend/src/pages/admin/metrics.vue
deleted file mode 100644
index f32b52d30a..0000000000
--- a/packages/frontend/src/pages/admin/metrics.vue
+++ /dev/null
@@ -1,439 +0,0 @@
-<template>
-<div class="_debobigegoItem">
-	<div class="_debobigegoLabel"><i class="fas fa-microchip"></i> {{ $ts.cpuAndMemory }}</div>
-	<div class="_debobigegoPanel xhexznfu">
-		<div>
-			<canvas :ref="cpumem"></canvas>
-		</div>
-		<div v-if="serverInfo">
-			<div class="_table">
-				<div class="_row">
-					<div class="_cell"><div class="_label">MEM total</div>{{ bytes(serverInfo.mem.total) }}</div>
-					<div class="_cell"><div class="_label">MEM used</div>{{ bytes(memUsage) }} ({{ (memUsage / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
-					<div class="_cell"><div class="_label">MEM free</div>{{ bytes(serverInfo.mem.total - memUsage) }} ({{ ((serverInfo.mem.total - memUsage) / serverInfo.mem.total * 100).toFixed(0) }}%)</div>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
-<div class="_debobigegoItem">
-	<div class="_debobigegoLabel"><i class="fas fa-hdd"></i> {{ $ts.disk }}</div>
-	<div class="_debobigegoPanel xhexznfu">
-		<div>
-			<canvas :ref="disk"></canvas>
-		</div>
-		<div v-if="serverInfo">
-			<div class="_table">
-				<div class="_row">
-					<div class="_cell"><div class="_label">Disk total</div>{{ bytes(serverInfo.fs.total) }}</div>
-					<div class="_cell"><div class="_label">Disk used</div>{{ bytes(serverInfo.fs.used) }} ({{ (serverInfo.fs.used / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
-					<div class="_cell"><div class="_label">Disk free</div>{{ bytes(serverInfo.fs.total - serverInfo.fs.used) }} ({{ ((serverInfo.fs.total - serverInfo.fs.used) / serverInfo.fs.total * 100).toFixed(0) }}%)</div>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
-<div class="_debobigegoItem">
-	<div class="_debobigegoLabel"><i class="fas fa-exchange-alt"></i> {{ $ts.network }}</div>
-	<div class="_debobigegoPanel xhexznfu">
-		<div>
-			<canvas :ref="net"></canvas>
-		</div>
-		<div v-if="serverInfo">
-			<div class="_table">
-				<div class="_row">
-					<div class="_cell"><div class="_label">Interface</div>{{ serverInfo.net.interface }}</div>
-				</div>
-			</div>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, markRaw } from 'vue';
-import { Chart } from 'chart.js';
-import MkwFederation from '../../widgets/federation.vue';
-import MkButton from '@/components/MkButton.vue';
-import MkSelect from '@/components/form/select.vue';
-import MkInput from '@/components/form/input.vue';
-import MkContainer from '@/components/MkContainer.vue';
-import MkFolder from '@/components/MkFolder.vue';
-import { version, url } from '@/config';
-import bytes from '@/filters/bytes';
-import number from '@/filters/number';
-import * as os from '@/os';
-import { stream } from '@/stream';
-import { alpha } from '@/scripts/color';
-import { initChart } from '@/scripts/init-chart';
-
-initChart();
-
-export default defineComponent({
-	components: {
-		MkButton,
-		MkSelect,
-		MkInput,
-		MkContainer,
-		MkFolder,
-		MkwFederation,
-	},
-
-	data() {
-		return {
-			version,
-			url,
-			stats: null,
-			serverInfo: null,
-			connection: null,
-			queueConnection: markRaw(stream.useChannel('queueStats')),
-			memUsage: 0,
-			chartCpuMem: null,
-			chartNet: null,
-			jobs: [],
-			logs: [],
-			logLevel: 'all',
-			logDomain: '',
-			modLogs: [],
-			dbInfo: null,
-			overviewHeight: '1fr',
-			queueHeight: '1fr',
-			paused: false,
-		};
-	},
-
-	computed: {
-		gridColor() {
-			// TODO: var(--panel)の色が暗いか明るいかで判定する
-			return this.$store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)';
-		},
-	},
-
-	mounted() {
-		this.fetchJobs();
-
-		Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg');
-
-		os.api('admin/server-info', {}).then(res => {
-			this.serverInfo = res;
-
-			this.connection = markRaw(stream.useChannel('serverStats'));
-			this.connection.on('stats', this.onStats);
-			this.connection.on('statsLog', this.onStatsLog);
-			this.connection.send('requestLog', {
-				id: Math.random().toString().substr(2, 8),
-				length: 150,
-			});
-
-			this.$nextTick(() => {
-				this.queueConnection.send('requestLog', {
-					id: Math.random().toString().substr(2, 8),
-					length: 200,
-				});
-			});
-		});
-	},
-
-	beforeUnmount() {
-		if (this.connection) {
-			this.connection.off('stats', this.onStats);
-			this.connection.off('statsLog', this.onStatsLog);
-			this.connection.dispose();
-		}
-		this.queueConnection.dispose();
-	},
-
-	methods: {
-		cpumem(el) {
-			if (this.chartCpuMem != null) return;
-			this.chartCpuMem = markRaw(new Chart(el, {
-				type: 'line',
-				data: {
-					labels: [],
-					datasets: [{
-						label: 'CPU',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#86b300',
-						backgroundColor: alpha('#86b300', 0.1),
-						data: [],
-					}, {
-						label: 'MEM (active)',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#935dbf',
-						backgroundColor: alpha('#935dbf', 0.02),
-						data: [],
-					}, {
-						label: 'MEM (used)',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#935dbf',
-						borderDash: [5, 5],
-						fill: false,
-						data: [],
-					}],
-				},
-				options: {
-					aspectRatio: 3,
-					layout: {
-						padding: {
-							left: 16,
-							right: 16,
-							top: 16,
-							bottom: 0,
-						},
-					},
-					legend: {
-						position: 'bottom',
-						labels: {
-							boxWidth: 16,
-						},
-					},
-					scales: {
-						x: {
-							gridLines: {
-								display: false,
-								color: this.gridColor,
-								zeroLineColor: this.gridColor,
-							},
-							ticks: {
-								display: false,
-							},
-						},
-						y: {
-							position: 'right',
-							gridLines: {
-								display: true,
-								color: this.gridColor,
-								zeroLineColor: this.gridColor,
-							},
-							ticks: {
-								display: false,
-								max: 100,
-							},
-						},
-					},
-					tooltips: {
-						intersect: false,
-						mode: 'index',
-					},
-				},
-			}));
-		},
-
-		net(el) {
-			if (this.chartNet != null) return;
-			this.chartNet = markRaw(new Chart(el, {
-				type: 'line',
-				data: {
-					labels: [],
-					datasets: [{
-						label: 'In',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#94a029',
-						backgroundColor: alpha('#94a029', 0.1),
-						data: [],
-					}, {
-						label: 'Out',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#ff9156',
-						backgroundColor: alpha('#ff9156', 0.1),
-						data: [],
-					}],
-				},
-				options: {
-					aspectRatio: 3,
-					layout: {
-						padding: {
-							left: 16,
-							right: 16,
-							top: 16,
-							bottom: 0,
-						},
-					},
-					legend: {
-						position: 'bottom',
-						labels: {
-							boxWidth: 16,
-						},
-					},
-					scales: {
-						x: {
-							gridLines: {
-								display: false,
-								color: this.gridColor,
-								zeroLineColor: this.gridColor,
-							},
-							ticks: {
-								display: false,
-							},
-						},
-						y: {
-							position: 'right',
-							gridLines: {
-								display: true,
-								color: this.gridColor,
-								zeroLineColor: this.gridColor,
-							},
-							ticks: {
-								display: false,
-							},
-						},
-					},
-					tooltips: {
-						intersect: false,
-						mode: 'index',
-					},
-				},
-			}));
-		},
-
-		disk(el) {
-			if (this.chartDisk != null) return;
-			this.chartDisk = markRaw(new Chart(el, {
-				type: 'line',
-				data: {
-					labels: [],
-					datasets: [{
-						label: 'Read',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#94a029',
-						backgroundColor: alpha('#94a029', 0.1),
-						data: [],
-					}, {
-						label: 'Write',
-						pointRadius: 0,
-						tension: 0,
-						borderWidth: 2,
-						borderColor: '#ff9156',
-						backgroundColor: alpha('#ff9156', 0.1),
-						data: [],
-					}],
-				},
-				options: {
-					aspectRatio: 3,
-					layout: {
-						padding: {
-							left: 16,
-							right: 16,
-							top: 16,
-							bottom: 0,
-						},
-					},
-					legend: {
-						position: 'bottom',
-						labels: {
-							boxWidth: 16,
-						},
-					},
-					scales: {
-						x: {
-							gridLines: {
-								display: false,
-								color: this.gridColor,
-								zeroLineColor: this.gridColor,
-							},
-							ticks: {
-								display: false,
-							},
-						},
-						y: {
-							position: 'right',
-							gridLines: {
-								display: true,
-								color: this.gridColor,
-								zeroLineColor: this.gridColor,
-							},
-							ticks: {
-								display: false,
-							},
-						},
-					},
-					tooltips: {
-						intersect: false,
-						mode: 'index',
-					},
-				},
-			}));
-		},
-
-		fetchJobs() {
-			os.api('admin/queue/deliver-delayed', {}).then(jobs => {
-				this.jobs = jobs;
-			});
-		},
-
-		onStats(stats) {
-			if (this.paused) return;
-
-			const cpu = (stats.cpu * 100).toFixed(0);
-			const memActive = (stats.mem.active / this.serverInfo.mem.total * 100).toFixed(0);
-			const memUsed = (stats.mem.used / this.serverInfo.mem.total * 100).toFixed(0);
-			this.memUsage = stats.mem.active;
-
-			this.chartCpuMem.data.labels.push('');
-			this.chartCpuMem.data.datasets[0].data.push(cpu);
-			this.chartCpuMem.data.datasets[1].data.push(memActive);
-			this.chartCpuMem.data.datasets[2].data.push(memUsed);
-			this.chartNet.data.labels.push('');
-			this.chartNet.data.datasets[0].data.push(stats.net.rx);
-			this.chartNet.data.datasets[1].data.push(stats.net.tx);
-			this.chartDisk.data.labels.push('');
-			this.chartDisk.data.datasets[0].data.push(stats.fs.r);
-			this.chartDisk.data.datasets[1].data.push(stats.fs.w);
-			if (this.chartCpuMem.data.datasets[0].data.length > 150) {
-				this.chartCpuMem.data.labels.shift();
-				this.chartCpuMem.data.datasets[0].data.shift();
-				this.chartCpuMem.data.datasets[1].data.shift();
-				this.chartCpuMem.data.datasets[2].data.shift();
-				this.chartNet.data.labels.shift();
-				this.chartNet.data.datasets[0].data.shift();
-				this.chartNet.data.datasets[1].data.shift();
-				this.chartDisk.data.labels.shift();
-				this.chartDisk.data.datasets[0].data.shift();
-				this.chartDisk.data.datasets[1].data.shift();
-			}
-			this.chartCpuMem.update();
-			this.chartNet.update();
-			this.chartDisk.update();
-		},
-
-		onStatsLog(statsLog) {
-			for (const stats of [...statsLog].reverse()) {
-				this.onStats(stats);
-			}
-		},
-
-		bytes,
-
-		number,
-
-		pause() {
-			this.paused = true;
-		},
-
-		resume() {
-			this.paused = false;
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.xhexznfu {
-	> div:nth-child(2) {
-		padding: 16px;
-		border-top: solid 0.5px var(--divider);
-	}
-}
-</style>

From 826e4502cb45441614e4881fab9307f85f4fd069 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 13:09:24 +0900
Subject: [PATCH 15/24] refactor

---
 packages/frontend/src/components/MkChart.vue                | 1 -
 packages/frontend/src/components/MkHeatmap.vue              | 3 +--
 packages/frontend/src/components/MkRetentionHeatmap.vue     | 3 +--
 packages/frontend/src/pages/admin/overview.active-users.vue | 3 +--
 packages/frontend/src/pages/admin/overview.vue              | 1 -
 packages/frontend/src/pages/user/activity.heatmap.vue       | 1 -
 packages/frontend/src/pages/user/activity.pv.vue            | 3 +--
 packages/frontend/src/scripts/init-chart.ts                 | 1 +
 8 files changed, 5 insertions(+), 11 deletions(-)

diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index 7d3a3d289b..c0562f02e3 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -15,7 +15,6 @@
 */
 import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue';
 import { Chart } from 'chart.js';
-import 'chartjs-adapter-date-fns';
 import { enUS } from 'date-fns/locale';
 import gradient from 'chartjs-plugin-gradient';
 import * as os from '@/os';
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index fff93ef965..ec7f49beee 100644
--- a/packages/frontend/src/components/MkHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -12,11 +12,10 @@ import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick, w
 import { Chart } from 'chart.js';
 import { enUS } from 'date-fns/locale';
 import tinycolor from 'tinycolor2';
+import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
 import * as os from '@/os';
-import 'chartjs-adapter-date-fns';
 import { defaultStore } from '@/store';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
-import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import { initChart } from '@/scripts/init-chart';
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index 95690bde47..e91b58a4a8 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -12,11 +12,10 @@ import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick }
 import { Chart } from 'chart.js';
 import { enUS } from 'date-fns/locale';
 import tinycolor from 'tinycolor2';
+import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
 import * as os from '@/os';
-import 'chartjs-adapter-date-fns';
 import { defaultStore } from '@/store';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
-import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import { initChart } from '@/scripts/init-chart';
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index e01009b7aa..14b09f34e9 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -12,11 +12,10 @@ import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick }
 import { Chart } from 'chart.js';
 import { enUS } from 'date-fns/locale';
 import tinycolor from 'tinycolor2';
+import gradient from 'chartjs-plugin-gradient';
 import * as os from '@/os';
-import 'chartjs-adapter-date-fns';
 import { defaultStore } from '@/store';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
-import gradient from 'chartjs-plugin-gradient';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import { initChart } from '@/scripts/init-chart';
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index c16a928a80..2e0b49c5a3 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -77,7 +77,6 @@ import * as os from '@/os';
 import { stream } from '@/stream';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
-import 'chartjs-adapter-date-fns';
 import { defaultStore } from '@/store';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
 import MkFolder from '@/components/MkFolder.vue';
diff --git a/packages/frontend/src/pages/user/activity.heatmap.vue b/packages/frontend/src/pages/user/activity.heatmap.vue
index 8472f79948..252985d113 100644
--- a/packages/frontend/src/pages/user/activity.heatmap.vue
+++ b/packages/frontend/src/pages/user/activity.heatmap.vue
@@ -14,7 +14,6 @@ import { enUS } from 'date-fns/locale';
 import tinycolor from 'tinycolor2';
 import * as misskey from 'misskey-js';
 import * as os from '@/os';
-import 'chartjs-adapter-date-fns';
 import { defaultStore } from '@/store';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 import { chartVLine } from '@/scripts/chart-vline';
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index eaaeba976c..2d83d1ddc3 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -13,11 +13,10 @@ import { Chart } from 'chart.js';
 import { enUS } from 'date-fns/locale';
 import tinycolor from 'tinycolor2';
 import * as misskey from 'misskey-js';
+import gradient from 'chartjs-plugin-gradient';
 import * as os from '@/os';
-import 'chartjs-adapter-date-fns';
 import { defaultStore } from '@/store';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip';
-import gradient from 'chartjs-plugin-gradient';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import { initChart } from '@/scripts/init-chart';
diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts
index 005ac5e271..fc18869009 100644
--- a/packages/frontend/src/scripts/init-chart.ts
+++ b/packages/frontend/src/scripts/init-chart.ts
@@ -20,6 +20,7 @@ import gradient from 'chartjs-plugin-gradient';
 import zoomPlugin from 'chartjs-plugin-zoom';
 import { MatrixController, MatrixElement } from 'chartjs-chart-matrix';
 import { defaultStore } from '@/store';
+import 'chartjs-adapter-date-fns';
 
 export function initChart() {
 	Chart.register(

From c93f091ba8c6f6d9ecf3c9a5c5a17bd532a58f54 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 13:37:32 +0900
Subject: [PATCH 16/24] perf(client): use shallowRef instead of ref for
 template reference

---
 packages/frontend/src/components/MkAbuseReportWindow.vue  | 4 ++--
 packages/frontend/src/components/MkCropperDialog.vue      | 2 +-
 packages/frontend/src/components/MkDialog.vue             | 4 ++--
 packages/frontend/src/components/MkDrive.vue              | 2 +-
 packages/frontend/src/components/MkDriveSelectDialog.vue  | 4 ++--
 packages/frontend/src/components/MkEmojiPickerDialog.vue  | 6 +++---
 .../frontend/src/components/MkFileCaptionEditWindow.vue   | 2 +-
 packages/frontend/src/components/MkImageViewer.vue        | 2 +-
 packages/frontend/src/components/MkLaunchPad.vue          | 2 +-
 packages/frontend/src/components/MkMenu.vue               | 2 +-
 packages/frontend/src/components/MkModalPageWindow.vue    | 2 +-
 packages/frontend/src/components/MkModalWindow.vue        | 2 +-
 packages/frontend/src/components/MkNote.vue               | 2 +-
 packages/frontend/src/components/MkNoteDetailed.vue       | 2 +-
 packages/frontend/src/components/MkNotes.vue              | 4 ++--
 .../src/components/MkNotificationSettingWindow.vue        | 2 +-
 packages/frontend/src/components/MkNotifications.vue      | 4 ++--
 packages/frontend/src/components/MkPageWindow.vue         | 2 +-
 packages/frontend/src/components/MkPopupMenu.vue          | 2 +-
 packages/frontend/src/components/MkPostFormDialog.vue     | 4 ++--
 packages/frontend/src/components/MkSigninDialog.vue       | 2 +-
 packages/frontend/src/components/MkSignupDialog.vue       | 2 +-
 .../frontend/src/components/MkTokenGenerateWindow.vue     | 2 +-
 packages/frontend/src/components/MkUpdated.vue            | 4 ++--
 packages/frontend/src/components/MkUserList.vue           | 6 ++----
 packages/frontend/src/components/MkVisibilityPicker.vue   | 2 +-
 packages/frontend/src/components/MkWaitingDialog.vue      | 4 ++--
 packages/frontend/src/pages/admin/abuses.vue              | 2 +-
 packages/frontend/src/pages/admin/emojis.vue              | 4 ++--
 packages/frontend/src/pages/admin/overview.queue.vue      | 8 ++++----
 packages/frontend/src/pages/admin/queue.chart.vue         | 8 ++++----
 packages/frontend/src/pages/admin/users.vue               | 2 +-
 packages/frontend/src/pages/antenna-timeline.vue          | 2 +-
 packages/frontend/src/pages/explore.users.vue             | 2 +-
 packages/frontend/src/pages/explore.vue                   | 2 +-
 packages/frontend/src/pages/favorites.vue                 | 4 +---
 packages/frontend/src/pages/follow-requests.vue           | 4 ++--
 packages/frontend/src/pages/messaging/messaging-room.vue  | 4 ++--
 packages/frontend/src/pages/my-clips/index.vue            | 2 +-
 packages/frontend/src/pages/my-lists/index.vue            | 2 +-
 packages/frontend/src/pages/settings/notifications.vue    | 2 +-
 packages/frontend/src/pages/timeline.vue                  | 2 +-
 packages/frontend/src/pages/user-list-timeline.vue        | 2 +-
 packages/frontend/src/ui/deck/antenna-column.vue          | 2 +-
 packages/frontend/src/ui/deck/list-column.vue             | 2 +-
 packages/frontend/src/widgets/instance-cloud.vue          | 2 +-
 46 files changed, 66 insertions(+), 70 deletions(-)

diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue
index 039f77c859..ab90ed357a 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.vue
+++ b/packages/frontend/src/components/MkAbuseReportWindow.vue
@@ -23,7 +23,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue';
+import { ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XWindow from '@/components/MkWindow.vue';
 import MkTextarea from '@/components/form/textarea.vue';
@@ -40,7 +40,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const uiWindow = ref<InstanceType<typeof XWindow>>();
+const uiWindow = shallowRef<InstanceType<typeof XWindow>>();
 const comment = ref(props.initialComment || '');
 
 function send() {
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 3ff73d6669..f00fef12f1 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -50,7 +50,7 @@ const props = defineProps<{
 }>();
 
 const imgUrl = getProxiedImageUrl(props.file.url);
-let dialogEl = $ref<InstanceType<typeof XModalWindow>>();
+let dialogEl = $shallowRef<InstanceType<typeof XModalWindow>>();
 let imgEl = $shallowRef<HTMLImageElement>();
 let cropper: Cropper | null = null;
 let loading = $ref(true);
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 374ecd8abf..18c9f9203c 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -39,7 +39,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onBeforeUnmount, onMounted, ref } from 'vue';
+import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
@@ -94,7 +94,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = ref<InstanceType<typeof MkModal>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 const inputValue = ref(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 843c5f1c54..112a64f52d 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -118,7 +118,7 @@ const emit = defineEmits<{
 	(ev: 'open-folder', v: Misskey.entities.DriveFolder): void;
 }>();
 
-const loadMoreFiles = ref<InstanceType<typeof MkButton>>();
+const loadMoreFiles = shallowRef<InstanceType<typeof MkButton>>();
 const fileInput = shallowRef<HTMLInputElement>();
 
 const folder = ref<Misskey.entities.DriveFolder | null>(null);
diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue
index 3ee821b539..6a96e758fa 100644
--- a/packages/frontend/src/components/MkDriveSelectDialog.vue
+++ b/packages/frontend/src/components/MkDriveSelectDialog.vue
@@ -19,7 +19,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import XDrive from '@/components/MkDrive.vue';
 import XModalWindow from '@/components/MkModalWindow.vue';
@@ -38,7 +38,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = ref<InstanceType<typeof XModalWindow>>();
+const dialog = shallowRef<InstanceType<typeof XModalWindow>>();
 
 const selected = ref<Misskey.entities.DriveFile[]>([]);
 
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 3b41f9d75b..da68ecd809 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -26,7 +26,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
 import { defaultStore } from '@/store';
@@ -48,8 +48,8 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = ref<InstanceType<typeof MkModal>>();
-const picker = ref<InstanceType<typeof MkEmojiPicker>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
+const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
 
 function chosen(emoji: any) {
 	emit('done', emoji);
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index e6b6c468ac..b3bd194dc3 100644
--- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue
+++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
@@ -37,7 +37,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = $ref<InstanceType<typeof XModalWindow>>();
+const dialog = $shallowRef<InstanceType<typeof XModalWindow>>();
 
 let caption = $ref(props.default);
 
diff --git a/packages/frontend/src/components/MkImageViewer.vue b/packages/frontend/src/components/MkImageViewer.vue
index f074b1a2f2..22b7c12e38 100644
--- a/packages/frontend/src/components/MkImageViewer.vue
+++ b/packages/frontend/src/components/MkImageViewer.vue
@@ -28,7 +28,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const modal = $ref<InstanceType<typeof MkModal>>();
+const modal = $shallowRef<InstanceType<typeof MkModal>>();
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 1ccc648c72..3ea90712a0 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -44,7 +44,7 @@ const preferedModalType = (deviceKind === 'desktop' && props.src != null) ? 'pop
 	deviceKind === 'smartphone' ? 'drawer' :
 	'dialog';
 
-const modal = $ref<InstanceType<typeof MkModal>>();
+const modal = $shallowRef<InstanceType<typeof MkModal>>();
 
 const menu = defaultStore.state.menu;
 
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 0db0767fac..808b2e8b81 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -82,7 +82,7 @@ let itemsEl = $shallowRef<HTMLDivElement>();
 
 let items2: InnerMenuItem[] = $ref([]);
 
-let child = $ref<InstanceType<typeof XChild>>();
+let child = $shallowRef<InstanceType<typeof XChild>>();
 
 let keymap = $computed(() => ({
 	'up|k|shift+tab': focusUp,
diff --git a/packages/frontend/src/components/MkModalPageWindow.vue b/packages/frontend/src/components/MkModalPageWindow.vue
index 645da6a49c..2791d5ceb9 100644
--- a/packages/frontend/src/components/MkModalPageWindow.vue
+++ b/packages/frontend/src/components/MkModalPageWindow.vue
@@ -49,7 +49,7 @@ router.addListener('push', ctx => {
 
 let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
 let rootEl = $ref();
-let modal = $ref<InstanceType<typeof MkModal>>();
+let modal = $shallowRef<InstanceType<typeof MkModal>>();
 let path = $ref(props.initialPath);
 let width = $ref(860);
 let height = $ref(660);
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index bd7146ab88..1e93f01c8d 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -41,7 +41,7 @@ const emit = defineEmits<{
 	(event: 'ok'): void;
 }>();
 
-let modal = $ref<InstanceType<typeof MkModal>>();
+let modal = $shallowRef<InstanceType<typeof MkModal>>();
 let rootEl = $shallowRef<HTMLElement>();
 let headerEl = $shallowRef<HTMLElement>();
 let bodyWidth = $ref(0);
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 4bbd44122d..883368a21a 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -158,7 +158,7 @@ const isRenote = (
 
 const el = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
-const renoteButton = ref<InstanceType<typeof MkRenoteButton>>();
+const renoteButton = shallowRef<InstanceType<typeof MkRenoteButton>>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 31188704ab..bac9c58b50 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -168,7 +168,7 @@ const isRenote = (
 
 const el = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
-const renoteButton = ref<InstanceType<typeof MkRenoteButton>>();
+const renoteButton = shallowRef<InstanceType<typeof MkRenoteButton>>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index 5abcdc2298..0d77597531 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -18,7 +18,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { shallowRef } from 'vue';
 import XNote from '@/components/MkNote.vue';
 import XList from '@/components/MkDateSeparatedList.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
@@ -29,7 +29,7 @@ const props = defineProps<{
 	noGap?: boolean;
 }>();
 
-const pagingComponent = ref<InstanceType<typeof MkPagination>>();
+const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
 defineExpose({
 	pagingComponent,
diff --git a/packages/frontend/src/components/MkNotificationSettingWindow.vue b/packages/frontend/src/components/MkNotificationSettingWindow.vue
index 92165ef476..754d8d687b 100644
--- a/packages/frontend/src/components/MkNotificationSettingWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSettingWindow.vue
@@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{
 
 let includingTypes = $computed(() => props.includingTypes || []);
 
-const dialog = $ref<InstanceType<typeof XModalWindow>>();
+const dialog = $shallowRef<InstanceType<typeof XModalWindow>>();
 
 let typesMap = $ref<Record<typeof notificationTypes[number], boolean>>({});
 let useGlobalSetting = $ref((includingTypes === null || includingTypes.length === 0) && props.showGlobalToggle);
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 0e1cc06743..18aa76deeb 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -17,7 +17,7 @@
 </template>
 
 <script lang="ts" setup>
-import { defineComponent, markRaw, onUnmounted, onMounted, computed, ref } from 'vue';
+import { defineComponent, markRaw, onUnmounted, onMounted, computed, shallowRef } from 'vue';
 import { notificationTypes } from 'misskey-js';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import XNotification from '@/components/MkNotification.vue';
@@ -33,7 +33,7 @@ const props = defineProps<{
 	unreadOnly?: boolean;
 }>();
 
-const pagingComponent = ref<InstanceType<typeof MkPagination>>();
+const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
 const pagination: Paging = {
 	endpoint: 'i/notifications' as const,
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index d47823c71f..e25737d50c 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -47,7 +47,7 @@ defineEmits<{
 const router = new Router(routes, props.initialPath);
 
 let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-let windowEl = $ref<InstanceType<typeof XWindow>>();
+let windowEl = $shallowRef<InstanceType<typeof XWindow>>();
 const history = $ref<{ path: string; key: any; }[]>([{
 	path: router.getCurrentPath(),
 	key: router.getCurrentKey(),
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index f04c7f5618..b5987715a9 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -22,7 +22,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let modal = $ref<InstanceType<typeof MkModal>>();
+let modal = $shallowRef<InstanceType<typeof MkModal>>();
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index 71c07ed658..f71dbdfdab 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -31,8 +31,8 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-let modal = $ref<InstanceType<typeof MkModal>>();
-let form = $ref<InstanceType<typeof MkPostForm>>();
+let modal = $shallowRef<InstanceType<typeof MkModal>>();
+let form = $shallowRef<InstanceType<typeof MkPostForm>>();
 
 function onPosted() {
 	modal.close({
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index fd27244516..5015d09e64 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -32,7 +32,7 @@ const emit = defineEmits<{
 	(ev: 'cancelled'): void;
 }>();
 
-const dialog = $ref<InstanceType<typeof XModalWindow>>();
+const dialog = $shallowRef<InstanceType<typeof XModalWindow>>();
 
 function onClose() {
 	emit('cancelled');
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 77497021c3..e1b76474a0 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -33,7 +33,7 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const dialog = $ref<InstanceType<typeof XModalWindow>>();
+const dialog = $shallowRef<InstanceType<typeof XModalWindow>>();
 
 function onSignup(res) {
 	emit('done', res);
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index b846034a24..8d5b6f8635 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -54,7 +54,7 @@ const emit = defineEmits<{
 	(ev: 'done', result: { name: string | null, permissions: string[] }): void;
 }>();
 
-const dialog = $ref<InstanceType<typeof XModalWindow>>();
+const dialog = $shallowRef<InstanceType<typeof XModalWindow>>();
 let name = $ref(props.initialName);
 let permissions = $ref({});
 
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 48aeb30224..2f2864220e 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -10,14 +10,14 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
 import { version } from '@/config';
 import { i18n } from '@/i18n';
 
-const modal = ref<InstanceType<typeof MkModal>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 const whatIsNew = () => {
 	modal.value.close();
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index e1f47c7673..b7bc200a55 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -1,5 +1,5 @@
 <template>
-<MkPagination ref="pagingComponent" :pagination="pagination">
+<MkPagination :pagination="pagination">
 	<template #empty>
 		<div class="_fullinfo">
 			<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -16,7 +16,7 @@
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { shallowRef } from 'vue';
 import MkUserInfo from '@/components/MkUserInfo.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import { userPage } from '@/filters/user';
@@ -26,8 +26,6 @@ const props = defineProps<{
 	pagination: Paging;
 	noGap?: boolean;
 }>();
-
-const pagingComponent = ref<InstanceType<typeof MkPagination>>();
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 8f0bcdeae8..99b5d93d5f 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -48,7 +48,7 @@ import * as misskey from 'misskey-js';
 import MkModal from '@/components/MkModal.vue';
 import { i18n } from '@/i18n';
 
-const modal = $ref<InstanceType<typeof MkModal>>();
+const modal = $shallowRef<InstanceType<typeof MkModal>>();
 
 const props = withDefaults(defineProps<{
 	currentVisibility: typeof misskey.noteVisibilities[number];
diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue
index f4a9f4f22c..984cdc5266 100644
--- a/packages/frontend/src/components/MkWaitingDialog.vue
+++ b/packages/frontend/src/components/MkWaitingDialog.vue
@@ -9,10 +9,10 @@
 </template>
 
 <script lang="ts" setup>
-import { watch, ref } from 'vue';
+import { watch, shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 
-const modal = ref<InstanceType<typeof MkModal>>();
+const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 const props = defineProps<{
 	success: boolean;
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 973ec871ab..3bff312b8b 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -58,7 +58,7 @@ import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
-let reports = $ref<InstanceType<typeof MkPagination>>();
+let reports = $shallowRef<InstanceType<typeof MkPagination>>();
 
 let state = $ref('unresolved');
 let reporterOrigin = $ref('combined');
diff --git a/packages/frontend/src/pages/admin/emojis.vue b/packages/frontend/src/pages/admin/emojis.vue
index 4bd78a6a5f..3d56ab1962 100644
--- a/packages/frontend/src/pages/admin/emojis.vue
+++ b/packages/frontend/src/pages/admin/emojis.vue
@@ -68,7 +68,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, defineAsyncComponent, defineComponent, ref, toRef } from 'vue';
+import { computed, defineAsyncComponent, defineComponent, ref, shallowRef } from 'vue';
 import XHeader from './_header_.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/form/input.vue';
@@ -81,7 +81,7 @@ import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
-const emojisPaginationComponent = ref<InstanceType<typeof MkPagination>>();
+const emojisPaginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
 const tab = ref('local');
 const query = ref(null);
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index 72ebddc72f..7e58882938 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -43,10 +43,10 @@ const activeSincePrevTick = ref(0);
 const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
-let chartProcess = $ref<InstanceType<typeof XChart>>();
-let chartActive = $ref<InstanceType<typeof XChart>>();
-let chartDelayed = $ref<InstanceType<typeof XChart>>();
-let chartWaiting = $ref<InstanceType<typeof XChart>>();
+let chartProcess = $shallowRef<InstanceType<typeof XChart>>();
+let chartActive = $shallowRef<InstanceType<typeof XChart>>();
+let chartDelayed = $shallowRef<InstanceType<typeof XChart>>();
+let chartWaiting = $shallowRef<InstanceType<typeof XChart>>();
 
 const props = defineProps<{
 	domain: string;
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 186a22c43e..7f1dc2cd77 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -53,10 +53,10 @@ const active = ref(0);
 const delayed = ref(0);
 const waiting = ref(0);
 const jobs = ref([]);
-let chartProcess = $ref<InstanceType<typeof XChart>>();
-let chartActive = $ref<InstanceType<typeof XChart>>();
-let chartDelayed = $ref<InstanceType<typeof XChart>>();
-let chartWaiting = $ref<InstanceType<typeof XChart>>();
+let chartProcess = $shallowRef<InstanceType<typeof XChart>>();
+let chartActive = $shallowRef<InstanceType<typeof XChart>>();
+let chartDelayed = $shallowRef<InstanceType<typeof XChart>>();
+let chartWaiting = $shallowRef<InstanceType<typeof XChart>>();
 
 const props = defineProps<{
 	domain: string;
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 24ce98a02f..16fa45b1fc 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -65,7 +65,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import { dateString } from '@/filters/date';
 
-let paginationComponent = $ref<InstanceType<typeof MkPagination>>();
+let paginationComponent = $shallowRef<InstanceType<typeof MkPagination>>();
 
 let sort = $ref('+createdAt');
 let state = $ref('all');
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index fd98ef5604..af7e95d543 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -35,7 +35,7 @@ const props = defineProps<{
 let antenna = $ref(null);
 let queue = $ref(0);
 let rootEl = $shallowRef<HTMLElement>();
-let tlEl = $ref<InstanceType<typeof XTimeline>>();
+let tlEl = $shallowRef<InstanceType<typeof XTimeline>>();
 const keymap = $computed(() => ({
 	't': focus,
 }));
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index bfee0a6c07..611ca0f003 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -72,7 +72,7 @@ const props = defineProps<{
 }>();
 
 let origin = $ref('local');
-let tagsEl = $ref<InstanceType<typeof MkFolder>>();
+let tagsEl = $shallowRef<InstanceType<typeof MkFolder>>();
 let tagsLocal = $ref([]);
 let tagsRemote = $ref([]);
 
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index 3f6c239bc1..4494f6154d 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -51,7 +51,7 @@ const props = withDefaults(defineProps<{
 });
 
 let tab = $ref(props.initialTab);
-let tagsEl = $ref<InstanceType<typeof MkFolder>>();
+let tagsEl = $shallowRef<InstanceType<typeof MkFolder>>();
 let searchQuery = $ref(null);
 let searchOrigin = $ref('combined');
 
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index ab47efec71..7fd660f93b 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
 	<MkSpacer :content-max="800">
-		<MkPagination ref="pagingComponent" :pagination="pagination">
+		<MkPagination :pagination="pagination">
 			<template #empty>
 				<div class="_fullinfo">
 					<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -33,8 +33,6 @@ const pagination = {
 	limit: 10,
 };
 
-const pagingComponent = ref<InstanceType<typeof MkPagination>>();
-
 definePageMetadata({
 	title: i18n.ts.favorites,
 	icon: 'ti ti-star',
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index b9d614b356..99401e1fc7 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -35,14 +35,14 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { shallowRef, computed } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import { userPage, acct } from '@/filters/user';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
-const paginationComponent = ref<InstanceType<typeof MkPagination>>();
+const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
 const pagination = {
 	endpoint: 'following/requests/list' as const,
diff --git a/packages/frontend/src/pages/messaging/messaging-room.vue b/packages/frontend/src/pages/messaging/messaging-room.vue
index 0db431b083..f0a36fb8b1 100644
--- a/packages/frontend/src/pages/messaging/messaging-room.vue
+++ b/packages/frontend/src/pages/messaging/messaging-room.vue
@@ -72,8 +72,8 @@ const props = defineProps<{
 }>();
 
 let rootEl = $shallowRef<HTMLDivElement>();
-let formEl = $ref<InstanceType<typeof XForm>>();
-let pagingComponent = $ref<InstanceType<typeof MkPagination>>();
+let formEl = $shallowRef<InstanceType<typeof XForm>>();
+let pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
 
 let fetching = $ref(true);
 let user: Misskey.entities.UserDetailed | null = $ref(null);
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index dd6b5b3a37..6c0508134f 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -29,7 +29,7 @@ const pagination = {
 	limit: 10,
 };
 
-const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
+const pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
 
 async function create() {
 	const { canceled, result } = await os.form(i18n.ts.createNewClip, {
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 3476436b27..510e0173df 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -25,7 +25,7 @@ import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
-const pagingComponent = $ref<InstanceType<typeof MkPagination>>();
+const pagingComponent = $shallowRef<InstanceType<typeof MkPagination>>();
 
 const pagination = {
 	endpoint: 'users/lists/list' as const,
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index e85fede157..c1b7130245 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -34,7 +34,7 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 
-let allowButton = $ref<InstanceType<typeof MkPushNotificationAllowButton>>();
+let allowButton = $shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
 let pushRegistrationInServer = $computed(() => allowButton?.pushRegistrationInServer);
 let sendReadMessage = $computed(() => pushRegistrationInServer?.sendReadMessage || false);
 
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 640d940d91..75f62d38f7 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -41,7 +41,7 @@ const keymap = {
 	't': focus,
 };
 
-const tlComponent = $ref<InstanceType<typeof XTimeline>>();
+const tlComponent = $shallowRef<InstanceType<typeof XTimeline>>();
 const rootEl = $shallowRef<HTMLElement>();
 
 let queue = $ref(0);
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index b69bbd575f..542c280594 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -34,7 +34,7 @@ const props = defineProps<{
 
 let list = $ref(null);
 let queue = $ref(0);
-let tlEl = $ref<InstanceType<typeof XTimeline>>();
+let tlEl = $shallowRef<InstanceType<typeof XTimeline>>();
 let rootEl = $shallowRef<HTMLElement>();
 
 watch(() => props.listId, async () => {
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index ba14530662..53d744676c 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -26,7 +26,7 @@ const emit = defineEmits<{
 	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
-let timeline = $ref<InstanceType<typeof XTimeline>>();
+let timeline = $shallowRef<InstanceType<typeof XTimeline>>();
 
 onMounted(() => {
 	if (props.column.antennaId == null) {
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index d9f3f7b4e7..e31446ebb2 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -26,7 +26,7 @@ const emit = defineEmits<{
 	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
 }>();
 
-let timeline = $ref<InstanceType<typeof XTimeline>>();
+let timeline = $shallowRef<InstanceType<typeof XTimeline>>();
 
 if (props.column.listId == null) {
 	setList();
diff --git a/packages/frontend/src/widgets/instance-cloud.vue b/packages/frontend/src/widgets/instance-cloud.vue
index 6d522ddbe8..1068c5ac4b 100644
--- a/packages/frontend/src/widgets/instance-cloud.vue
+++ b/packages/frontend/src/widgets/instance-cloud.vue
@@ -45,7 +45,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-let cloud = $ref<InstanceType<typeof MkTagCloud> | null>();
+let cloud = $shallowRef<InstanceType<typeof MkTagCloud> | null>();
 let activeInstances = $shallowRef(null);
 
 function onInstanceClick(i) {

From fb05e86db72740933292f105da3fce43b8fafb1f Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 14:08:52 +0900
Subject: [PATCH 17/24] :art:

---
 packages/frontend/src/components/MkChart.vue  | 13 ++--
 .../frontend/src/components/MkChartLegend.vue | 75 +++++++++++++++++++
 .../frontend/src/pages/user/activity.pv.vue   | 15 ++--
 packages/frontend/src/scripts/chart-legend.ts | 12 +++
 packages/frontend/src/scripts/chart-vline.ts  |  4 +-
 5 files changed, 103 insertions(+), 16 deletions(-)
 create mode 100644 packages/frontend/src/components/MkChartLegend.vue
 create mode 100644 packages/frontend/src/scripts/chart-legend.ts

diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index c0562f02e3..ea28cfa794 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -1,6 +1,7 @@
 <template>
 <div class="cbbedffa">
 	<canvas ref="chartEl"></canvas>
+	<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
 	<div v-if="fetching" class="fetching">
 		<MkLoading/>
 	</div>
@@ -24,6 +25,8 @@ import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import date from '@/filters/date';
 import { initChart } from '@/scripts/init-chart';
+import { chartLegend } from '@/scripts/chart-legend';
+import MkChartLegend from '@/components/MkChartLegend.vue';
 
 initChart();
 
@@ -67,6 +70,8 @@ const props = defineProps({
 	},
 });
 
+let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+
 const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b));
 const negate = arr => arr.map(x => -x);
 
@@ -220,11 +225,7 @@ const render = () => {
 			},
 			plugins: {
 				legend: {
-					display: props.detailed,
-					position: 'bottom',
-					labels: {
-						boxWidth: 16,
-					},
+					display: false,
 				},
 				tooltip: {
 					enabled: false,
@@ -264,7 +265,7 @@ const render = () => {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor)],
+		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl)] : [])],
 	});
 };
 
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
new file mode 100644
index 0000000000..f33f753723
--- /dev/null
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -0,0 +1,75 @@
+<template>
+<div :class="$style.root">
+	<button v-for="item in items" class="_button item" :class="{ disabled: item.hidden }" @click="onClick(item)">
+		<span class="box" :style="{ background: chart.config.type === 'line' ? item.strokeStyle?.toString() : item.fillStyle?.toString() }"></span>
+		{{ item.text }}
+	</button>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref, shallowRef, watch, PropType, onUnmounted } from 'vue';
+import { Chart, LegendItem } from 'chart.js';
+
+const props = defineProps({
+});
+
+let chart = $shallowRef<Chart>();
+let items = $shallowRef<LegendItem[]>([]);
+
+function update(_chart: Chart, _items: LegendItem[]) {
+	chart = _chart,
+	items = _items;
+}
+
+function onClick(item: LegendItem) {
+	if (chart == null) return;
+	const { type } = chart.config;
+	if (type === 'pie' || type === 'doughnut') {
+		// Pie and doughnut charts only have a single dataset and visibility is per item
+		chart.toggleDataVisibility(item.index);
+	} else {
+		chart.setDatasetVisibility(item.datasetIndex, !chart.isDatasetVisible(item.datasetIndex));
+	}
+	chart.update();
+}
+
+defineExpose({
+	update,
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	display: flex;
+	flex-wrap: wrap;
+	justify-content: center;
+	gap: 8px;
+
+	&:global {
+		> .item {
+			font-size: 85%;
+			padding: 4px 12px 4px 8px;
+			border: solid 1px var(--divider);
+			border-radius: 999px;
+
+			&:hover {
+				border-color: var(--inputBorderHover);
+			}
+
+			&.disabled {
+				text-decoration: line-through;
+				opacity: 0.6;
+			}
+
+			> .box {
+				display: inline-block;
+				width: 12px;
+				height: 12px;
+				border-radius: 100%;
+				vertical-align: -10%;
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 2d83d1ddc3..7715b66673 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -3,6 +3,7 @@
 	<MkLoading v-if="fetching"/>
 	<div v-show="!fetching" :class="$style.root" class="_panel">
 		<canvas ref="chartEl"></canvas>
+		<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
 	</div>
 </div>
 </template>
@@ -20,6 +21,8 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 import { chartVLine } from '@/scripts/chart-vline';
 import { alpha } from '@/scripts/color';
 import { initChart } from '@/scripts/init-chart';
+import { chartLegend } from '@/scripts/chart-legend';
+import MkChartLegend from '@/components/MkChartLegend.vue';
 
 initChart();
 
@@ -28,6 +31,7 @@ const props = defineProps<{
 }>();
 
 const chartEl = $shallowRef<HTMLCanvasElement>(null);
+let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
 const now = new Date();
 let chartInstance: Chart = null;
 const chartLimit = 30;
@@ -153,14 +157,7 @@ async function renderChart() {
 					},
 				},
 				legend: {
-					display: true,
-					position: 'bottom',
-					padding: {
-						left: 0,
-						right: 0,
-						top: 8,
-						bottom: 0,
-					},
+					display: false,
 				},
 				tooltip: {
 					enabled: false,
@@ -173,7 +170,7 @@ async function renderChart() {
 				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor)],
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
 	});
 
 	fetching = false;
diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/scripts/chart-legend.ts
new file mode 100644
index 0000000000..6a5370cc87
--- /dev/null
+++ b/packages/frontend/src/scripts/chart-legend.ts
@@ -0,0 +1,12 @@
+import { Plugin } from 'chart.js';
+import MkChartLegend from '@/components/MkChartLegend.vue';
+
+export const chartLegend = (legend: InstanceType<typeof MkChartLegend>) => ({
+	id: 'htmlLegend',
+	afterUpdate(chart, args, options) {
+		// Reuse the built-in legendItems generator
+		const items = chart.options.plugins.legend.labels.generateLabels(chart);
+
+		legend.update(chart, items);
+	},
+}) as Plugin;
diff --git a/packages/frontend/src/scripts/chart-vline.ts b/packages/frontend/src/scripts/chart-vline.ts
index 10021583e0..f321443834 100644
--- a/packages/frontend/src/scripts/chart-vline.ts
+++ b/packages/frontend/src/scripts/chart-vline.ts
@@ -1,3 +1,5 @@
+import { Plugin } from 'chart.js';
+
 export const chartVLine = (vLineColor: string) => ({
 	id: 'vLine',
 	beforeDraw(chart, args, options) {
@@ -18,4 +20,4 @@ export const chartVLine = (vLineColor: string) => ({
 			ctx.restore();
 		}
 	},
-});
+}) as Plugin;

From 2d4d3417a2ac2236af0cb60a2d206c7e229526ee Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 14:17:39 +0900
Subject: [PATCH 18/24] refactor

---
 .../src/components/MkVisibilityPicker.vue     | 40 +++++++++----------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 99b5d93d5f..4c78659f5c 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -1,42 +1,42 @@
 <template>
 <MkModal ref="modal" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')">
 	<div class="gqyayizv _popup">
-		<button key="public" class="_button" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')">
-			<div><i class="ti ti-world"></i></div>
-			<div>
+		<button key="public" class="_button item" :class="{ active: v === 'public' }" data-index="1" @click="choose('public')">
+			<div class="icon"><i class="ti ti-world"></i></div>
+			<div class="body">
 				<span>{{ i18n.ts._visibility.public }}</span>
 				<span>{{ i18n.ts._visibility.publicDescription }}</span>
 			</div>
 		</button>
-		<button key="home" class="_button" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')">
-			<div><i class="ti ti-home"></i></div>
-			<div>
+		<button key="home" class="_button item" :class="{ active: v === 'home' }" data-index="2" @click="choose('home')">
+			<div class="icon"><i class="ti ti-home"></i></div>
+			<div class="body">
 				<span>{{ i18n.ts._visibility.home }}</span>
 				<span>{{ i18n.ts._visibility.homeDescription }}</span>
 			</div>
 		</button>
-		<button key="followers" class="_button" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
-			<div><i class="ti ti-lock-open"></i></div>
-			<div>
+		<button key="followers" class="_button item" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
+			<div class="icon"><i class="ti ti-lock-open"></i></div>
+			<div class="body">
 				<span>{{ i18n.ts._visibility.followers }}</span>
 				<span>{{ i18n.ts._visibility.followersDescription }}</span>
 			</div>
 		</button>
-		<button key="specified" :disabled="localOnly" class="_button" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')">
-			<div><i class="ti ti-mail"></i></div>
-			<div>
+		<button key="specified" :disabled="localOnly" class="_button item" :class="{ active: v === 'specified' }" data-index="4" @click="choose('specified')">
+			<div class="icon"><i class="ti ti-mail"></i></div>
+			<div class="body">
 				<span>{{ i18n.ts._visibility.specified }}</span>
 				<span>{{ i18n.ts._visibility.specifiedDescription }}</span>
 			</div>
 		</button>
 		<div class="divider"></div>
-		<button key="localOnly" class="_button localOnly" :class="{ active: localOnly }" data-index="5" @click="localOnly = !localOnly">
-			<div><i class="ti ti-world-off"></i></div>
-			<div>
+		<button key="localOnly" class="_button item localOnly" :class="{ active: localOnly }" data-index="5" @click="localOnly = !localOnly">
+			<div class="icon"><i class="ti ti-world-off"></i></div>
+			<div class="body">
 				<span>{{ i18n.ts._visibility.localOnly }}</span>
 				<span>{{ i18n.ts._visibility.localOnlyDescription }}</span>
 			</div>
-			<div><i :class="localOnly ? 'ti ti-toggle-right' : 'ti ti-toggle-left'"></i></div>
+			<div class="toggle"><i :class="localOnly ? 'ti ti-toggle-right' : 'ti ti-toggle-left'"></i></div>
 		</button>
 	</div>
 </MkModal>
@@ -89,7 +89,7 @@ function choose(visibility: typeof misskey.noteVisibilities[number]): void {
 		border-top: solid 0.5px var(--divider);
 	}
 
-	> button {
+	> .item {
 		display: flex;
 		padding: 8px 14px;
 		font-size: 12px;
@@ -115,7 +115,7 @@ function choose(visibility: typeof misskey.noteVisibilities[number]): void {
 			background: inherit;
 		}
 
-		> *:nth-child(1) {
+		> .icon {
 			display: flex;
 			justify-content: center;
 			align-items: center;
@@ -127,7 +127,7 @@ function choose(visibility: typeof misskey.noteVisibilities[number]): void {
 			margin-bottom: auto;
 		}
 
-		> *:nth-child(2) {
+		> .body {
 			flex: 1 1 auto;
 			white-space: nowrap;
 			overflow: hidden;
@@ -143,7 +143,7 @@ function choose(visibility: typeof misskey.noteVisibilities[number]): void {
 			}
 		}
 
-		> *:nth-child(3) {
+		> .toggle {
 			display: flex;
 			justify-content: center;
 			align-items: center;

From 3500e035cd6db065dd581eb03f284a88cbc4790e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 14:21:32 +0900
Subject: [PATCH 19/24] refactor

---
 packages/frontend/src/components/MkMenu.vue         | 4 ----
 packages/frontend/src/components/MkNote.vue         | 4 ++--
 packages/frontend/src/components/MkNoteDetailed.vue | 4 ++--
 3 files changed, 4 insertions(+), 8 deletions(-)

diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 808b2e8b81..263030e015 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -226,10 +226,6 @@ onBeforeUnmount(() => {
 			border-radius: 6px;
 		}
 
-		> * {
-			position: relative;
-		}
-
 		&:not(:disabled):hover {
 			color: var(--accent);
 			text-decoration: none;
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 883368a21a..5a97204bc5 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -59,7 +59,7 @@
 					</div>
 					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
-					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote"/></div>
+					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div>
 					<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
 						<span>{{ i18n.ts.showMore }}</span>
 					</button>
@@ -529,7 +529,7 @@ function readPromo() {
 					> .renote {
 						padding: 8px 0;
 
-						> * {
+						> .note {
 							padding: 16px;
 							border: dashed 1px var(--renote);
 							border-radius: 8px;
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index bac9c58b50..7d01a7bf75 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -70,7 +70,7 @@
 					</div>
 					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" class="url-preview"/>
-					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote"/></div>
+					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div>
 				</div>
 				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
@@ -491,7 +491,7 @@ if (appearNote.replyId) {
 					> .renote {
 						padding: 8px 0;
 
-						> * {
+						> .note {
 							padding: 16px;
 							border: dashed 1px var(--renote);
 							border-radius: 8px;

From 8ee78d97bb9de5202e53da1f47c8526ff2021bdc Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 14:21:57 +0900
Subject: [PATCH 20/24] 13.0.0-beta.19

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ec1fa7ef43..22f56296d9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.18",
+	"version": "13.0.0-beta.19",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From db1ca28de36be37e07f7d4eb206adeb2480755ec Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 14:27:18 +0900
Subject: [PATCH 21/24] clean up

---
 .../backend/src/queue/processors/DeliverProcessorService.ts  | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index d62e1f643a..c5e4a66517 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -48,7 +48,6 @@ export class DeliverProcessorService {
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
 		this.suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60);
-		this.latest = null;
 	}
 
 	@bindThis
@@ -76,10 +75,6 @@ export class DeliverProcessorService {
 		}
 
 		try {
-			if (this.latest !== (this.latest = JSON.stringify(job.data.content, null, 2))) {
-				this.logger.debug(`delivering ${this.latest}`);
-			}
-
 			await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
 
 			// Update stats

From e44f83fb9c13e4fab8aee07603632d533d12b4d8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 15:51:49 +0900
Subject: [PATCH 22/24] update AiScript to 0.12.0

---
 CHANGELOG.md                                  |  3 +
 packages/backend/package.json                 |  1 -
 packages/frontend/package.json                |  2 +-
 .../frontend/src/components/page/page.vue     | 30 +--------
 packages/frontend/src/pages/scratchpad.vue    |  8 ++-
 .../frontend/src/pages/settings/general.vue   |  2 -
 .../src/pages/settings/plugin.install.vue     | 31 +++++++---
 .../pages/settings/preferences-backups.vue    |  1 -
 packages/frontend/src/plugin.ts               | 15 ++---
 .../frontend/src/scripts/hpml/evaluator.ts    | 61 ++-----------------
 packages/frontend/src/scripts/hpml/lib.ts     |  7 +--
 packages/frontend/src/store.ts                |  4 --
 packages/frontend/src/widgets/aiscript.vue    |  8 ++-
 packages/frontend/src/widgets/button.vue      |  8 ++-
 yarn.lock                                     | 51 +++++-----------
 15 files changed, 75 insertions(+), 157 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3b39c66042..284399b55e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -20,6 +20,9 @@ You should also include the user name that made the change.
 	- You may have to `yarn run clean-all`, `sudo corepack enable` and `yarn set version berry` before running `yarn install` if you're still on yarn classic
 - 新たに動的なPagesを作ることはできなくなりました
 	- 代わりに今後AiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能の実装を予定しています。
+- AiScriptが0.12.0にアップデートされました
+	- 0.12.0の変更点についてはこちら https://github.com/syuilo/aiscript/blob/master/CHANGELOG.md#0120
+	- 0.12.0未満のプラグインは読み込むことはできません
 - iOS15以下のデバイスはサポートされなくなりました
 - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
 	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
diff --git a/packages/backend/package.json b/packages/backend/package.json
index e4e3274c0b..e78b3ef70a 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -37,7 +37,6 @@
 		"@nestjs/testing": "9.2.1",
 		"@peertube/http-signature": "1.7.0",
 		"@sinonjs/fake-timers": "10.0.2",
-		"@syuilo/aiscript": "0.11.1",
 		"accepts": "^1.3.8",
 		"ajv": "8.11.2",
 		"archiver": "5.3.1",
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index e380165681..2506e8e9d3 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -11,7 +11,7 @@
 		"@rollup/plugin-alias": "4.0.2",
 		"@rollup/plugin-json": "6.0.0",
 		"@rollup/pluginutils": "5.0.2",
-		"@syuilo/aiscript": "0.11.1",
+		"@syuilo/aiscript": "0.12.0",
 		"@tabler/icons": "^1.118.0",
 		"@vitejs/plugin-vue": "4.0.0",
 		"@vue/compiler-sfc": "3.2.45",
diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue
index b5cb73c009..87a288befe 100644
--- a/packages/frontend/src/components/page/page.vue
+++ b/packages/frontend/src/components/page/page.vue
@@ -6,7 +6,6 @@
 
 <script lang="ts">
 import { defineComponent, onMounted, nextTick, onUnmounted, PropType } from 'vue';
-import { parse } from '@syuilo/aiscript';
 import XBlock from './page.block.vue';
 import { Hpml } from '@/scripts/hpml/evaluator';
 import { url } from '@/config';
@@ -28,38 +27,11 @@ export default defineComponent({
 			randomSeed: Math.random(),
 			visitor: $i,
 			url: url,
-			enableAiScript: !defaultStore.state.disablePagesScript,
 		});
 
 		onMounted(() => {
 			nextTick(() => {
-				if (props.page.script && hpml.aiscript) {
-					let ast;
-					try {
-						ast = parse(props.page.script);
-					} catch (err) {
-						console.error(err);
-						/*os.alert({
-							type: 'error',
-							text: 'Syntax error :('
-						});*/
-						return;
-					}
-					hpml.aiscript.exec(ast).then(() => {
-						hpml.eval();
-					}).catch(err => {
-						console.error(err);
-						/*os.alert({
-							type: 'error',
-							text: err
-						});*/
-					});
-				} else {
-					hpml.eval();
-				}
-			});
-			onUnmounted(() => {
-				if (hpml.aiscript) hpml.aiscript.abort();
+				hpml.eval();
 			});
 		});
 
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index edb2d8e18c..9db17efc03 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -27,7 +27,7 @@ import 'prismjs/components/prism-javascript';
 import 'prismjs/themes/prism-okaidia.css';
 import { PrismEditor } from 'vue-prism-editor';
 import 'vue-prism-editor/dist/prismeditor.min.css';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
+import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import MkContainer from '@/components/MkContainer.vue';
 import MkButton from '@/components/MkButton.vue';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
@@ -36,6 +36,8 @@ import { $i } from '@/account';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
+const parser = new Parser();
+
 const code = ref('');
 const logs = ref<any[]>([]);
 
@@ -50,7 +52,7 @@ watch(code, () => {
 
 async function run() {
 	logs.value = [];
-	const aiscript = new AiScript(createAiScriptEnv({
+	const aiscript = new Interpreter(createAiScriptEnv({
 		storageKey: 'scratchpad',
 		token: $i?.token,
 	}), {
@@ -84,7 +86,7 @@ async function run() {
 
 	let ast;
 	try {
-		ast = parse(code.value);
+		ast = parser.parse(code.value);
 	} catch (error) {
 		os.alert({
 			type: 'error',
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 84d99d2fd7..b426ccfa0a 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -27,7 +27,6 @@
 		<FormSwitch v-model="imageNewTab" class="_formBlock">{{ i18n.ts.openImageInNewTab }}</FormSwitch>
 		<FormSwitch v-model="enableInfiniteScroll" class="_formBlock">{{ i18n.ts.enableInfiniteScroll }}</FormSwitch>
 		<FormSwitch v-model="useReactionPickerForContextMenu" class="_formBlock">{{ i18n.ts.useReactionPickerForContextMenu }}</FormSwitch>
-		<FormSwitch v-model="disablePagesScript" class="_formBlock">{{ i18n.ts.disablePagesScript }}</FormSwitch>
 
 		<FormSelect v-model="serverDisconnectedBehavior" class="_formBlock">
 			<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@@ -141,7 +140,6 @@ const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('dis
 const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
 const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
 const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
-const disablePagesScript = computed(defaultStore.makeGetterSetter('disablePagesScript'));
 const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
 const numberOfPageCache = computed(defaultStore.makeGetterSetter('numberOfPageCache'));
 const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'));
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue
index 550bba242e..40ad9a95dd 100644
--- a/packages/frontend/src/pages/settings/plugin.install.vue
+++ b/packages/frontend/src/pages/settings/plugin.install.vue
@@ -14,8 +14,7 @@
 
 <script lang="ts" setup>
 import { defineAsyncComponent, nextTick, ref } from 'vue';
-import { AiScript, parse } from '@syuilo/aiscript';
-import { serialize } from '@syuilo/aiscript/built/serializer';
+import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import { v4 as uuid } from 'uuid';
 import FormTextarea from '@/components/form/textarea.vue';
 import FormButton from '@/components/MkButton.vue';
@@ -26,23 +25,41 @@ import { unisonReload } from '@/scripts/unison-reload';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 
+const parser = new Parser();
 const code = ref(null);
 
-function installPlugin({ id, meta, ast, token }) {
+function installPlugin({ id, meta, src, token }) {
 	ColdDeviceStorage.set('plugins', ColdDeviceStorage.get('plugins').concat({
 		...meta,
 		id,
 		active: true,
 		configData: {},
 		token: token,
-		ast: ast,
+		src: src,
 	}));
 }
 
 async function install() {
+	if (code.value == null) return;
+
+	const lv = utils.getLangVersion(code.value);
+	if (lv == null) {
+		os.alert({
+			type: 'error',
+			text: 'No language version annotation found :(',
+		});
+		return;
+	} else if (lv !== '0.12.0') {
+		os.alert({
+			type: 'error',
+			text: `aiscript version '${lv}' is not supported :(`,
+		});
+		return;
+	}
+
 	let ast;
 	try {
-		ast = parse(code.value);
+		ast = parser.parse(code.value);
 	} catch (err) {
 		os.alert({
 			type: 'error',
@@ -51,7 +68,7 @@ async function install() {
 		return;
 	}
 
-	const meta = AiScript.collectMetadata(ast);
+	const meta = Interpreter.collectMetadata(ast);
 	if (meta == null) {
 		os.alert({
 			type: 'error',
@@ -103,7 +120,7 @@ async function install() {
 			name, version, author, description, permissions, config,
 		},
 		token,
-		ast: serialize(ast),
+		src: code.value,
 	});
 
 	os.success();
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 8b7548f25b..a713c1262d 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -62,7 +62,6 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'loadRawImages',
 	'imageNewTab',
 	'disableShowingAnimatedImages',
-	'disablePagesScript',
 	'emojiStyle',
 	'disableDrawer',
 	'useBlurEffectForModal',
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 3a00cd0455..c19fe2b08d 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -1,16 +1,17 @@
-import { AiScript, utils, values } from '@syuilo/aiscript';
-import { deserialize } from '@syuilo/aiscript/built/serializer';
-import { jsToVal } from '@syuilo/aiscript/built/interpreter/util';
+import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
 import { inputText } from '@/os';
 import { noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions } from '@/store';
 
-const pluginContexts = new Map<string, AiScript>();
+const parser = new Parser();
+const pluginContexts = new Map<string, Interpreter>();
 
 export function install(plugin) {
+	// 後方互換性のため
+	if (plugin.src == null) return;
 	console.info('Plugin installed:', plugin.name, 'v' + plugin.version);
 
-	const aiscript = new AiScript(createPluginEnv({
+	const aiscript = new Interpreter(createPluginEnv({
 		plugin: plugin,
 		storageKey: 'plugins:' + plugin.id,
 	}), {
@@ -32,13 +33,13 @@ export function install(plugin) {
 
 	initPlugin({ plugin, aiscript });
 
-	aiscript.exec(deserialize(plugin.ast));
+	aiscript.exec(parser.parse(plugin.src));
 }
 
 function createPluginEnv(opts) {
 	const config = new Map();
 	for (const [k, v] of Object.entries(opts.plugin.config || {})) {
-		config.set(k, jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default));
+		config.set(k, utils.jsToVal(typeof opts.plugin.configData[k] !== 'undefined' ? opts.plugin.configData[k] : v.default));
 	}
 
 	return {
diff --git a/packages/frontend/src/scripts/hpml/evaluator.ts b/packages/frontend/src/scripts/hpml/evaluator.ts
index 196b3142a1..d4090ea15c 100644
--- a/packages/frontend/src/scripts/hpml/evaluator.ts
+++ b/packages/frontend/src/scripts/hpml/evaluator.ts
@@ -1,13 +1,11 @@
 import autobind from 'autobind-decorator';
-import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
-import { version } from '@/config';
-import { AiScript, utils, values } from '@syuilo/aiscript';
-import { createAiScriptEnv } from '../aiscript/api';
+import { markRaw, ref, Ref, unref } from 'vue';
 import { collectPageVars } from '../collect-page-vars';
 import { initHpmlLib, initAiLib } from './lib';
-import * as os from '@/os';
-import { markRaw, ref, Ref, unref } from 'vue';
 import { Expr, isLiteralValue, Variable } from './expr';
+import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
+import { version } from '@/config';
+import * as os from '@/os';
 
 /**
  * Hpml evaluator
@@ -16,7 +14,6 @@ export class Hpml {
 	private variables: Variable[];
 	private pageVars: PageVar[];
 	private envVars: Record<keyof typeof envVarsDef, any>;
-	public aiscript?: AiScript;
 	public pageVarUpdatedCallback?: values.VFn;
 	public canvases: Record<string, HTMLCanvasElement> = {};
 	public vars: Ref<Record<string, any>> = ref({});
@@ -24,7 +21,6 @@ export class Hpml {
 
 	private opts: {
 		randomSeed: string; visitor?: any; url?: string;
-		enableAiScript: boolean;
 	};
 
 	constructor(page: Hpml['page'], opts: Hpml['opts']) {
@@ -33,31 +29,6 @@ export class Hpml {
 		this.pageVars = collectPageVars(this.page.content);
 		this.opts = opts;
 
-		if (this.opts.enableAiScript) {
-			this.aiscript = markRaw(new AiScript({ ...createAiScriptEnv({
-				storageKey: 'pages:' + this.page.id,
-			}), ...initAiLib(this) }, {
-				in: (q) => {
-					return new Promise(ok => {
-						os.inputText({
-							title: q,
-						}).then(({ canceled, result: a }) => {
-							ok(a);
-						});
-					});
-				},
-				out: (value) => {
-					console.log(value);
-				},
-				log: (type, params) => {
-				},
-			}));
-
-			this.aiscript.scope.opts.onUpdated = (name, value) => {
-				this.eval();
-			};
-		}
-
 		const date = new Date();
 
 		this.envVars = {
@@ -74,7 +45,7 @@ export class Hpml {
 			IS_CAT: opts.visitor ? opts.visitor.isCat : false,
 			SEED: opts.randomSeed ? opts.randomSeed : '',
 			YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`,
-			AISCRIPT_DISABLED: !this.opts.enableAiScript,
+			AISCRIPT_DISABLED: true,
 			NULL: null,
 		};
 
@@ -99,13 +70,6 @@ export class Hpml {
 		});
 	}
 
-	@autobind
-	public callAiScript(fn: string) {
-		try {
-			if (this.aiscript) this.aiscript.execFn(this.aiscript.scope.get(fn), []);
-		} catch (err) {}
-	}
-
 	@autobind
 	public registerCanvas(id: string, canvas: any) {
 		this.canvases[id] = canvas;
@@ -116,9 +80,6 @@ export class Hpml {
 		const pageVar = this.pageVars.find(v => v.name === name);
 		if (pageVar !== undefined) {
 			pageVar.value = value;
-			if (this.pageVarUpdatedCallback) {
-				if (this.aiscript) this.aiscript.execFn(this.pageVarUpdatedCallback, [values.STR(name), utils.jsToVal(value)]);
-			}
 		} else {
 			throw new HpmlError(`No such page var '${name}'`);
 		}
@@ -180,18 +141,6 @@ export class Hpml {
 				return scope.getState(expr.value);
 			}
 
-			if (expr.type === 'aiScriptVar') {
-				if (this.aiscript) {
-					try {
-						return utils.valToJs(this.aiscript.scope.get(expr.value));
-					} catch (err) {
-						return null;
-					}
-				} else {
-					return null;
-				}
-			}
-
 			// Define user function
 			if (expr.type === 'fn') {
 				return {
diff --git a/packages/frontend/src/scripts/hpml/lib.ts b/packages/frontend/src/scripts/hpml/lib.ts
index b684876a7f..02d663b31b 100644
--- a/packages/frontend/src/scripts/hpml/lib.ts
+++ b/packages/frontend/src/scripts/hpml/lib.ts
@@ -1,9 +1,8 @@
 import tinycolor from 'tinycolor2';
-import { Hpml } from './evaluator';
-import { values, utils } from '@syuilo/aiscript';
-import { Fn, HpmlScope } from '.';
-import { Expr } from './expr';
 import seedrandom from 'seedrandom';
+import { Hpml } from './evaluator';
+import { Expr } from './expr';
+import { Fn, HpmlScope } from '.';
 
 /* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color
 // https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 8e3e6b36da..4b1f47c2bc 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -170,10 +170,6 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
-	disablePagesScript: {
-		where: 'device',
-		default: false,
-	},
 	emojiStyle: {
 		where: 'device',
 		default: 'twemoji', // twemoji / fluentEmoji / native
diff --git a/packages/frontend/src/widgets/aiscript.vue b/packages/frontend/src/widgets/aiscript.vue
index 4009edb8b8..dcc73db201 100644
--- a/packages/frontend/src/widgets/aiscript.vue
+++ b/packages/frontend/src/widgets/aiscript.vue
@@ -14,7 +14,7 @@
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
+import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
@@ -52,6 +52,8 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
+const parser = new Parser();
+
 const logs = ref<{
 	id: string;
 	text: string;
@@ -60,7 +62,7 @@ const logs = ref<{
 
 const run = async () => {
 	logs.value = [];
-	const aiscript = new AiScript(createAiScriptEnv({
+	const aiscript = new Interpreter(createAiScriptEnv({
 		storageKey: 'widget',
 		token: $i?.token,
 	}), {
@@ -94,7 +96,7 @@ const run = async () => {
 
 	let ast;
 	try {
-		ast = parse(widgetProps.script);
+		ast = parser.parse(widgetProps.script);
 	} catch (err) {
 		os.alert({
 			type: 'error',
diff --git a/packages/frontend/src/widgets/button.vue b/packages/frontend/src/widgets/button.vue
index f0148d7f4e..f65b115cc0 100644
--- a/packages/frontend/src/widgets/button.vue
+++ b/packages/frontend/src/widgets/button.vue
@@ -8,7 +8,7 @@
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, ref, watch } from 'vue';
-import { AiScript, parse, utils } from '@syuilo/aiscript';
+import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
@@ -48,8 +48,10 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
+const parser = new Parser();
+
 const run = async () => {
-	const aiscript = new AiScript(createAiScriptEnv({
+	const aiscript = new Interpreter(createAiScriptEnv({
 		storageKey: 'widget',
 		token: $i?.token,
 	}), {
@@ -72,7 +74,7 @@ const run = async () => {
 
 	let ast;
 	try {
-		ast = parse(widgetProps.script);
+		ast = parser.parse(widgetProps.script);
 	} catch (err) {
 		os.alert({
 			type: 'error',
diff --git a/yarn.lock b/yarn.lock
index e5958bafe5..7ce29e1543 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1973,16 +1973,15 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@syuilo/aiscript@npm:0.11.1":
-  version: 0.11.1
-  resolution: "@syuilo/aiscript@npm:0.11.1"
+"@syuilo/aiscript@npm:0.12.0":
+  version: 0.12.0
+  resolution: "@syuilo/aiscript@npm:0.12.0"
   dependencies:
     autobind-decorator: 2.4.0
-    chalk: 4.0.0
     seedrandom: 3.0.5
     stringz: 2.1.0
-    uuid: 7.0.3
-  checksum: c5e15045abdf1393f1da9c58f25dd301bd2b6c5808f2ca58334e5d488c227b189ec5200ec86326ed381380a9ff76dbc05fd30882f562383be76b83dc4fabb11b
+    uuid: 8.3.2
+  checksum: 82b52a6c602a8c3090b9457a0e9de99898b03cd8f054855b2f57439534257ef2780013a53eaeeef68c9893d96d3ec02fc6d0ede56396c2bcf054cf43b2297b67
   languageName: node
   linkType: hard
 
@@ -4136,7 +4135,6 @@ __metadata:
     "@sinonjs/fake-timers": 10.0.2
     "@swc/core": 1.3.24
     "@swc/jest": 0.2.24
-    "@syuilo/aiscript": 0.11.1
     "@tensorflow/tfjs": ^4.1.0
     "@tensorflow/tfjs-node": 4.1.0
     "@types/accepts": 1.3.5
@@ -4869,16 +4867,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"chalk@npm:4.0.0":
-  version: 4.0.0
-  resolution: "chalk@npm:4.0.0"
-  dependencies:
-    ansi-styles: ^4.1.0
-    supports-color: ^7.1.0
-  checksum: a9580afd4af8ffa8add8edb565d1b3f77efb880c5d887d3bb72a948d1bfb2bc764db2ceb6d62a60103aa384f3da71eb1969c7f68e886055e0a3438550e809cde
-  languageName: node
-  linkType: hard
-
 "chalk@npm:5.2.0":
   version: 5.2.0
   resolution: "chalk@npm:5.2.0"
@@ -8038,7 +8026,7 @@ __metadata:
     "@rollup/plugin-alias": 4.0.2
     "@rollup/plugin-json": 6.0.0
     "@rollup/pluginutils": 5.0.2
-    "@syuilo/aiscript": 0.11.1
+    "@syuilo/aiscript": 0.12.0
     "@tabler/icons": ^1.118.0
     "@types/escape-regexp": 0.0.1
     "@types/glob": 8.0.0
@@ -16915,15 +16903,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"uuid@npm:7.0.3":
-  version: 7.0.3
-  resolution: "uuid@npm:7.0.3"
-  bin:
-    uuid: dist/bin/uuid
-  checksum: f5b7b5cc28accac68d5c083fd51cca64896639ebd4cca88c6cfb363801aaa83aa439c86dfc8446ea250a7a98d17afd2ad9e88d9d4958c79a412eccb93bae29de
-  languageName: node
-  linkType: hard
-
 "uuid@npm:8.0.0":
   version: 8.0.0
   resolution: "uuid@npm:8.0.0"
@@ -16933,6 +16912,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"uuid@npm:8.3.2, uuid@npm:^8.3.0, uuid@npm:^8.3.2":
+  version: 8.3.2
+  resolution: "uuid@npm:8.3.2"
+  bin:
+    uuid: dist/bin/uuid
+  checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df
+  languageName: node
+  linkType: hard
+
 "uuid@npm:9.0.0":
   version: 9.0.0
   resolution: "uuid@npm:9.0.0"
@@ -16951,15 +16939,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"uuid@npm:^8.3.0, uuid@npm:^8.3.2":
-  version: 8.3.2
-  resolution: "uuid@npm:8.3.2"
-  bin:
-    uuid: dist/bin/uuid
-  checksum: 5575a8a75c13120e2f10e6ddc801b2c7ed7d8f3c8ac22c7ed0c7b2ba6383ec0abda88c905085d630e251719e0777045ae3236f04c812184b7c765f63a70e58df
-  languageName: node
-  linkType: hard
-
 "v8-to-istanbul@npm:^9.0.1":
   version: 9.0.1
   resolution: "v8-to-istanbul@npm:9.0.1"

From 441321c17098310894844b27a2d518f2729c0454 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 16:01:23 +0900
Subject: [PATCH 23/24] 13.0.0-beta.20

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 22f56296d9..1e3bf82a7b 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.19",
+	"version": "13.0.0-beta.20",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 4903af9598fe18b837693b4db515257d0943f646 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Tue, 3 Jan 2023 17:00:15 +0900
Subject: [PATCH 24/24] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 284399b55e..0a5e566263 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ You should also include the user name that made the change.
 	- remote: `https://p1.a9z.dev/emoji/syuilo_birth_present@mk.f72u.net.webp`
 - API: `user`および`note`エンティティに`emojis`プロパティが含まれなくなりました
 - API: `user`エンティティに`avatarColor`および`bannerColor`プロパティが含まれなくなりました
+- API: `instance`エンティティに`latestStatus`、`lastCommunicatedAt`、`latestRequestSentAt`プロパティが含まれなくなりました
 
 ### Improvements
 - Push notification of Antenna note @tamaina