From 8b5768f8482573a4b1d709214dc762f7e9a7de97 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 1 Apr 2025 12:49:53 -0400
Subject: add DynamicNoteSimple.vue

---
 .../frontend/src/components/DynamicNoteSimple.vue  | 48 ++++++++++++++++++++++
 1 file changed, 48 insertions(+)
 create mode 100644 packages/frontend/src/components/DynamicNoteSimple.vue

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

diff --git a/packages/frontend/src/components/DynamicNoteSimple.vue b/packages/frontend/src/components/DynamicNoteSimple.vue
new file mode 100644
index 0000000000..a7713afad1
--- /dev/null
+++ b/packages/frontend/src/components/DynamicNoteSimple.vue
@@ -0,0 +1,48 @@
+<!--
+SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<XNoteSimple
+	ref="rootEl"
+	:note="note"
+	:expandAllCws="expandAllCws"
+	:hideFiles="hideFiles"
+	@editScheduledNote="() => emit('editScheduleNote')"
+/>
+</template>
+
+<script setup lang="ts">
+import * as Misskey from 'misskey-js';
+import { computed, defineAsyncComponent, shallowRef } from 'vue';
+import type { ComponentExposed } from 'vue-component-type-helpers';
+import type MkNoteSimple from '@/components/MkNoteSimple.vue';
+import type SkNoteSimple from '@/components/SkNoteSimple.vue';
+import { prefer } from '@/preferences';
+
+const XNoteSimple = computed(() =>
+	defineAsyncComponent<typeof MkNoteSimple | typeof SkNoteSimple>(() =>
+		prefer.r.noteDesign.value === 'misskey'
+			? import('@/components/MkNoteSimple.vue')
+			: import('@/components/SkNoteSimple.vue'),
+	),
+);
+
+const rootEl = shallowRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>();
+
+defineExpose({ rootEl });
+
+defineProps<{
+	note: Misskey.entities.Note & {
+		isSchedule?: boolean,
+		scheduledNoteId?: string
+	};
+	expandAllCws?: boolean;
+	hideFiles?: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'editScheduleNote'): void;
+}>();
+</script>
-- 
cgit v1.2.3-freya


From 6ac37b4d6cae064545b13fd7fdb414d0cffa178b Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Tue, 1 Apr 2025 20:47:04 -0400
Subject: lint and type fixes

---
 packages/backend/src/GlobalModule.ts               |  16 ++-
 packages/backend/src/core/ApLogService.ts          |  18 ++++
 packages/backend/src/core/NoteCreateService.ts     |  29 ++----
 .../backend/src/core/activitypub/misc/validator.ts |   2 +
 .../src/core/activitypub/models/ApPersonService.ts |   3 +-
 .../backend/src/core/entities/NoteEntityService.ts |  18 ++--
 packages/backend/src/misc/gen-identicon.ts         |   4 +-
 packages/backend/src/models/NoteSchedule.ts        |   4 +-
 packages/backend/src/models/UserProfile.ts         |   2 +-
 .../backend/src/models/json-schema/note-edit.ts    |  59 ------------
 .../processors/DeleteAccountProcessorService.ts    |   5 +
 .../src/queue/processors/InboxProcessorService.ts  |   3 -
 packages/backend/src/server/ServerModule.ts        |   3 -
 packages/backend/src/server/ServerService.ts       |  19 ++--
 .../backend/src/server/api/RateLimiterService.ts   | 107 ---------------------
 .../backend/src/server/api/SigninApiService.ts     |   9 +-
 .../backend/src/server/api/SignupApiService.ts     |   2 -
 .../src/server/api/StreamingApiServerService.ts    |   6 +-
 .../server/api/endpoints/admin/emoji/import-zip.ts |   2 +-
 .../server/api/endpoints/admin/gen-vapid-keys.ts   |   4 +-
 .../server/api/endpoints/admin/reset-password.ts   |   1 -
 .../src/server/api/endpoints/admin/update-meta.ts  |   8 +-
 .../backend/src/server/api/endpoints/ap/show.ts    |   3 +-
 .../api/endpoints/federation/update-remote-user.ts |   3 +-
 packages/backend/src/server/api/endpoints/i.ts     |   3 +-
 .../src/server/api/endpoints/i/2fa/key-done.ts     |   1 -
 .../src/server/api/endpoints/i/2fa/register-key.ts |   1 -
 .../src/server/api/endpoints/i/2fa/register.ts     |   1 -
 .../src/server/api/endpoints/i/2fa/remove-key.ts   |   1 -
 .../src/server/api/endpoints/i/2fa/unregister.ts   |   1 -
 .../src/server/api/endpoints/i/2fa/update-key.ts   |   1 -
 .../src/server/api/endpoints/i/change-password.ts  |   2 -
 .../src/server/api/endpoints/i/delete-account.ts   |   1 -
 .../src/server/api/endpoints/i/regenerate-token.ts |   1 -
 .../src/server/api/endpoints/i/update-email.ts     |   1 -
 .../server/api/endpoints/notes/global-timeline.ts  |   6 +-
 .../server/api/endpoints/notes/search-by-tag.ts    |   6 +-
 .../src/server/api/endpoints/notes/unrenote.ts     |   1 +
 .../src/server/api/endpoints/reset-password.ts     |   2 -
 .../src/server/api/endpoints/server-info.ts        |   3 +-
 .../backend/src/server/api/endpoints/users/show.ts |   3 +-
 .../backend/src/server/api/stream/Connection.ts    |   1 -
 .../src/server/oauth/OAuth2ProviderService.ts      |   3 -
 .../backend/src/server/web/ClientServerService.ts  |   2 +-
 packages/frontend-embed/src/components/EmNote.vue  |   1 -
 .../src/components/EmNoteDetailed.vue              |   1 -
 .../frontend-embed/src/components/EmNoteSimple.vue |   1 -
 .../frontend-embed/src/components/EmNoteSub.vue    |   1 -
 packages/frontend/src/components/DynamicNote.vue   |   4 +-
 .../src/components/DynamicNoteDetailed.vue         |   4 +-
 .../frontend/src/components/DynamicNoteSimple.vue  |   4 +-
 packages/frontend/src/components/MkCaptcha.vue     |   1 +
 .../frontend/src/components/MkImgWithBlurhash.vue  |   4 +-
 packages/frontend/src/components/MkNote.vue        |   8 +-
 .../frontend/src/components/MkNoteDetailed.vue     |   8 +-
 packages/frontend/src/components/MkNoteHeader.vue  |   8 +-
 packages/frontend/src/components/MkNoteSimple.vue  |   2 +-
 packages/frontend/src/components/MkNoteSub.vue     |   8 +-
 .../frontend/src/components/MkNotification.vue     |  29 +++---
 packages/frontend/src/components/MkOmit.vue        |   1 -
 .../frontend/src/components/MkSubNoteContent.vue   |   1 -
 packages/frontend/src/components/SkNote.vue        |   8 +-
 .../frontend/src/components/SkNoteDetailed.vue     |   8 +-
 packages/frontend/src/components/SkNoteSub.vue     |   4 +-
 .../frontend/src/components/page/page.text.vue     |   7 +-
 packages/frontend/src/pages/about.federation.vue   |   3 +-
 packages/frontend/src/pages/about.overview.vue     |  17 ++--
 packages/frontend/src/pages/admin-user.vue         |   1 +
 packages/frontend/src/pages/admin/moderation.vue   |   1 +
 packages/frontend/src/pages/admin/roles.vue        |   1 +
 packages/frontend/src/pages/favorites.vue          |   2 +-
 packages/frontend/src/pages/note.vue               |   2 +-
 .../frontend/src/pages/settings/drive-cleaner.vue  |   5 +-
 .../frontend/src/pages/settings/preferences.vue    |   1 -
 packages/frontend/src/pages/settings/profile.vue   |   1 -
 packages/frontend/src/pages/welcome.setup.vue      |   1 +
 packages/frontend/src/types/post-form.ts           |   1 +
 packages/frontend/src/ui/_common_/common.vue       |  12 +--
 packages/frontend/src/ui/_common_/navbar.vue       |   2 +-
 packages/frontend/src/use/use-note-capture.ts      |   2 +-
 packages/frontend/src/utility/deep-equal.ts        |   2 +-
 packages/frontend/src/utility/favicon-dot.ts       |  20 ++--
 packages/frontend/src/utility/get-note-menu.ts     |   2 +-
 packages/frontend/test/aiscript/ui.test.ts         |   2 +-
 84 files changed, 188 insertions(+), 374 deletions(-)
 delete mode 100644 packages/backend/src/models/json-schema/note-edit.ts
 delete mode 100644 packages/backend/src/server/api/RateLimiterService.ts

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

diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 92d1bf20fa..90dbdaf2a6 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -178,15 +178,13 @@ export class GlobalModule implements OnApplicationShutdown {
 		// Wait for all potential DB queries
 		await allSettled();
 		// And then disconnect from DB
-		await Promise.all([
-			this.db.destroy(),
-			this.redisClient.disconnect(),
-			this.redisForPub.disconnect(),
-			this.redisForSub.disconnect(),
-			this.redisForTimelines.disconnect(),
-			this.redisForReactions.disconnect(),
-			this.redisForRateLimit.disconnect(),
-		]);
+		await this.db.destroy();
+		this.redisClient.disconnect();
+		this.redisForPub.disconnect();
+		this.redisForSub.disconnect();
+		this.redisForTimelines.disconnect();
+		this.redisForReactions.disconnect();
+		this.redisForRateLimit.disconnect();
 	}
 
 	async onApplicationShutdown(signal: string): Promise<void> {
diff --git a/packages/backend/src/core/ApLogService.ts b/packages/backend/src/core/ApLogService.ts
index 096ec21de7..f21c6da313 100644
--- a/packages/backend/src/core/ApLogService.ts
+++ b/packages/backend/src/core/ApLogService.ts
@@ -139,6 +139,24 @@ export class ApLogService {
 		}
 	}
 
+	/**
+	 * Deletes all logged inbox activities from a user or users
+	 * @param userIds IDs of the users to delete
+	 */
+	public async deleteInboxLogs(userIds: string | string[]): Promise<number> {
+		if (Array.isArray(userIds)) {
+			const logsDeleted = await this.apInboxLogsRepository.delete({
+				authUserId: In(userIds),
+			});
+			return logsDeleted.affected ?? 0;
+		} else {
+			const logsDeleted = await this.apInboxLogsRepository.delete({
+				authUserId: userIds,
+			});
+			return logsDeleted.affected ?? 0;
+		}
+	}
+
 	/**
 	 * Deletes all expired AP logs and garbage-collects the AP context cache.
 	 * Returns the total number of deleted rows.
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 72df948c8b..98d9571255 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -571,7 +571,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		if (this.meta.enableStatsForFederatedInstances) {
 			if (this.userEntityService.isRemoteUser(user)) {
 				this.federatedInstanceService.fetchOrRegister(user.host).then(async i => {
-					if (note.renote && note.text || !note.renote) {
+					if (!this.isRenote(note) || this.isQuote(note)) {
 						this.updateNotesCountQueue.enqueue(i.id, 1);
 					}
 					if (this.meta.enableChartsForFederatedInstances) {
@@ -583,17 +583,12 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		// ハッシュタグ更新
 		if (data.visibility === 'public' || data.visibility === 'home') {
-			if (user.isBot && this.meta.enableBotTrending) {
-				this.hashtagService.updateHashtags(user, tags);
-			} else if (!user.isBot) {
+			if (!user.isBot || this.meta.enableBotTrending) {
 				this.hashtagService.updateHashtags(user, tags);
 			}
 		}
 
-		if (data.renote && data.text) {
-			// Increment notes count (user)
-			this.incNotesCountOfUser(user);
-		} else if (!data.renote) {
+		if (!this.isRenote(note) || this.isQuote(note)) {
 			// Increment notes count (user)
 			this.incNotesCountOfUser(user);
 		}
@@ -631,7 +626,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 			});
 		}
 
-		if (data.renote && data.text == null && data.renote.userId !== user.id && !user.isBot) {
+		if (this.isRenote(data) && !this.isQuote(data) && data.renote.userId !== user.id && !user.isBot) {
 			this.incRenoteCount(data.renote);
 		}
 
@@ -706,13 +701,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 						},
 					});
 
-					const [
-						userIdsWhoMeMuting,
-					] = data.renote.userId ? await Promise.all([
-						this.cacheService.userMutingsCache.fetch(data.renote.userId),
-					]) : [new Set<string>()];
-
-					const muted = isUserRelated(note, userIdsWhoMeMuting);
+					const muted = data.renote.userId && isUserRelated(note, await this.cacheService.userMutingsCache.fetch(data.renote.userId));
 
 					if (!isThreadMuted && !muted) {
 						nm.push(data.renote.userId, type);
@@ -848,13 +837,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 				},
 			});
 
-			const [
-				userIdsWhoMeMuting,
-			] = u.id ? await Promise.all([
-				this.cacheService.userMutingsCache.fetch(u.id),
-			]) : [new Set<string>()];
-
-			const muted = isUserRelated(note, userIdsWhoMeMuting);
+			const muted = u.id && isUserRelated(note, await this.cacheService.userMutingsCache.fetch(u.id));
 
 			if (isThreadMuted || muted) {
 				continue;
diff --git a/packages/backend/src/core/activitypub/misc/validator.ts b/packages/backend/src/core/activitypub/misc/validator.ts
index 4292b7e0f7..0ff83659c1 100644
--- a/packages/backend/src/core/activitypub/misc/validator.ts
+++ b/packages/backend/src/core/activitypub/misc/validator.ts
@@ -5,6 +5,8 @@
 
 import type { Response } from 'node-fetch';
 
+// TODO throw identifiable or unrecoverable errors
+
 export function validateContentTypeSetAsActivityPub(response: Response): void {
 	const contentType = (response.headers.get('content-type') ?? '').toLowerCase();
 
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index d7ee6c306b..c57c3f1704 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -322,6 +322,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown {
 
 		const host = this.utilityService.punyHost(uri);
 		if (host === this.utilityService.toPuny(this.config.host)) {
+			// TODO convert to unrecoverable error
 			throw new StatusError(`cannot resolve local user: ${uri}`, 400, 'cannot resolve local user');
 		}
 
@@ -570,7 +571,7 @@ export class ApPersonService implements OnModuleInit, OnApplicationShutdown {
 				.catch(err => {
 					if (!(err instanceof StatusError) || err.isRetryable) {
 						this.logger.error('error occurred while fetching following/followers collection', { stack: err });
-						// Do not update the visibiility on transient errors.
+						// Do not update the visibility on transient errors.
 						return undefined;
 					}
 					return 'private';
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index 537677ed34..c3d00ffa9d 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -479,14 +479,6 @@ export class NoteEntityService implements OnModuleInit {
 			mentions: note.mentions && note.mentions.length > 0 ? note.mentions : undefined,
 			uri: note.uri ?? undefined,
 			url: note.url ?? undefined,
-			poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
-			...(meId && Object.keys(reactions).length > 0 ? {
-				myReaction: this.populateMyReaction({
-					id: note.id,
-					reactions: reactions,
-					reactionAndUserPairCache: reactionAndUserPairCache,
-				}, meId, options?._hint_),
-			} : {}),
 
 			...(opts.detail ? {
 				clippedCount: note.clippedCount,
@@ -505,6 +497,16 @@ export class NoteEntityService implements OnModuleInit {
 					withReactionAndUserPairCache: opts.withReactionAndUserPairCache,
 					_hint_: options?._hint_,
 				}) : undefined,
+
+				poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,
+
+				...(meId && Object.keys(reactions).length > 0 ? {
+					myReaction: this.populateMyReaction({
+						id: note.id,
+						reactions: reactions,
+						reactionAndUserPairCache: reactionAndUserPairCache,
+					}, meId, options?._hint_),
+				} : {}),
 			} : {}),
 		});
 
diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts
index f3c08cc76e..ac7db82f2e 100644
--- a/packages/backend/src/misc/gen-identicon.ts
+++ b/packages/backend/src/misc/gen-identicon.ts
@@ -44,7 +44,7 @@ const sideN = Math.floor(n / 2);
 /**
  * Generate buffer of an identicon by seed
  */
-export async function genIdenticon(seed: string): Promise<Buffer> {
+export function genIdenticon(seed: string): Buffer {
 	const rand = gen.create(seed);
 	const canvas = createCanvas(size, size);
 	const ctx = canvas.getContext('2d');
@@ -100,5 +100,5 @@ export async function genIdenticon(seed: string): Promise<Buffer> {
 		}
 	}
 
-	return await canvas.toBuffer('image/png');
+	return canvas.toBuffer('image/png');
 }
diff --git a/packages/backend/src/models/NoteSchedule.ts b/packages/backend/src/models/NoteSchedule.ts
index dde0af6ad7..c9d031c281 100644
--- a/packages/backend/src/models/NoteSchedule.ts
+++ b/packages/backend/src/models/NoteSchedule.ts
@@ -17,7 +17,7 @@ type MinimumUser = {
 	uri: MiUser['uri'];
 };
 
-export type MiScheduleNoteType={
+export type MiScheduleNoteType = {
 	visibility: 'public' | 'home' | 'followers' | 'specified';
 	visibleUsers: MinimumUser[];
 	channel?: MiChannel['id'];
@@ -37,7 +37,7 @@ export type MiScheduleNoteType={
 	apMentions?: MinimumUser[] | null;
 	apHashtags?: string[] | null;
 	apEmojis?: string[] | null;
-}
+};
 
 @Entity('note_schedule')
 export class MiNoteSchedule {
diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts
index 449c2f370b..cda55451d0 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -4,7 +4,7 @@
  */
 
 import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
-import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes, noteVisibilities, defaultCWPriorities } from '@/types.js';
+import { obsoleteNotificationTypes, followingVisibilities, followersVisibilities, notificationTypes, defaultCWPriorities } from '@/types.js';
 import { id } from './util/id.js';
 import { MiUser } from './User.js';
 import { MiPage } from './Page.js';
diff --git a/packages/backend/src/models/json-schema/note-edit.ts b/packages/backend/src/models/json-schema/note-edit.ts
deleted file mode 100644
index ba936f866b..0000000000
--- a/packages/backend/src/models/json-schema/note-edit.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * SPDX-FileCopyrightText: marie and other Sharkey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export const packedNoteEdit = {
-	type: "object",
-	properties: {
-		id: {
-			type: "string",
-			optional: false,
-			nullable: false,
-			format: "id",
-			example: "xxxxxxxxxx",
-		},
-		updatedAt: {
-			type: "string",
-			optional: false,
-			nullable: false,
-			format: "date-time",
-		},
-		note: {
-			type: "object",
-			optional: false,
-			nullable: false,
-			ref: "Note",
-		},
-		noteId: {
-			type: "string",
-			optional: false,
-			nullable: false,
-			format: "id",
-		},
-		oldText: {
-			type: "string",
-			optional: true,
-			nullable: true,
-		},
-		newText: {
-			type: "string",
-			optional: true,
-			nullable: true,
-		},
-		cw: {
-			type: "string",
-			optional: true,
-			nullable: true,
-		},
-		fileIds: {
-			type: "array",
-			optional: true,
-			nullable: true,
-			items: {
-				type: "string",
-				format: "id",
-			},
-		},
-	},
-} as const;
diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
index 0c70829132..46cee096cf 100644
--- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
@@ -184,6 +184,11 @@ export class DeleteAccountProcessorService {
 				await this.apLogService.deleteObjectLogs(user.uri)
 					.catch(err => this.logger.error(err, `Failed to delete AP logs for user '${user.uri}'`));
 			}
+
+			await this.apLogService.deleteInboxLogs(user.id)
+				.catch(err => this.logger.error(err, `Failed to delete AP logs for user '${user.uri}'`));
+
+			this.logger.succ('All AP logs deleted');
 		}
 
 		{ // Send email notification
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index 50630e4061..9564724c62 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -25,8 +25,6 @@ import { JsonLdService } from '@/core/activitypub/JsonLdService.js';
 import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
 import { bindThis } from '@/decorators.js';
 import { IdentifiableError } from '@/misc/identifiable-error.js';
-//import { CollapsedQueue } from '@/misc/collapsed-queue.js';
-//import { MiNote } from '@/models/Note.js';
 import { MiMeta } from '@/models/Meta.js';
 import { DI } from '@/di-symbols.js';
 import { SkApInboxLog } from '@/models/_.js';
@@ -68,7 +66,6 @@ export class InboxProcessorService implements OnApplicationShutdown {
 		private readonly updateInstanceQueue: UpdateInstanceQueue,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('inbox');
-		//this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index c8f6e88fa9..6726d4aa67 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -28,7 +28,6 @@ import { ActivityPubServerService } from './ActivityPubServerService.js';
 import { ApiLoggerService } from './api/ApiLoggerService.js';
 import { ApiServerService } from './api/ApiServerService.js';
 import { AuthenticateService } from './api/AuthenticateService.js';
-import { RateLimiterService } from './api/RateLimiterService.js';
 import { SigninApiService } from './api/SigninApiService.js';
 import { SigninService } from './api/SigninService.js';
 import { SignupApiService } from './api/SignupApiService.js';
@@ -88,8 +87,6 @@ import { SigninWithPasskeyApiService } from './api/SigninWithPasskeyApiService.j
 		ApiServerService,
 		AuthenticateService,
 		SkRateLimiterService,
-		// No longer used, but kept for backwards compatibility
-		RateLimiterService,
 		SigninApiService,
 		SigninWithPasskeyApiService,
 		SigninService,
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 9ae8f2efe4..c90c206e94 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -229,12 +229,12 @@ export class ServerService implements OnApplicationShutdown {
 			}
 		});
 
-		fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => {
-			reply.header('Content-Type', 'image/png');
+		fastify.get<{ Params: { x: string } }>('/identicon/:x', (request, reply) => {
+ 			reply.header('Content-Type', 'image/png');
 			reply.header('Cache-Control', 'public, max-age=86400');
 
 			if (this.meta.enableIdenticonGeneration) {
-				return await genIdenticon(request.params.x);
+				return genIdenticon(request.params.x);
 			} else {
 				return reply.redirect('/static-assets/avatar.png');
 			}
@@ -293,13 +293,14 @@ export class ServerService implements OnApplicationShutdown {
 			if (fs.existsSync(this.config.socket)) {
 				fs.unlinkSync(this.config.socket);
 			}
-			fastify.listen({ path: this.config.socket }, (err, address) => {
-				if (this.config.chmodSocket) {
-					fs.chmodSync(this.config.socket!, this.config.chmodSocket);
-				}
-			});
+
+			await fastify.listen({ path: this.config.socket });
+
+			if (this.config.chmodSocket) {
+				fs.chmodSync(this.config.socket!, this.config.chmodSocket);
+			}
 		} else {
-			fastify.listen({ port: this.config.port, host: this.config.address });
+			await fastify.listen({ port: this.config.port, host: this.config.address });
 		}
 
 		await fastify.ready();
diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts
deleted file mode 100644
index 879529090f..0000000000
--- a/packages/backend/src/server/api/RateLimiterService.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { Inject, Injectable } from '@nestjs/common';
-import Limiter from 'ratelimiter';
-import * as Redis from 'ioredis';
-import { DI } from '@/di-symbols.js';
-import type Logger from '@/logger.js';
-import { LoggerService } from '@/core/LoggerService.js';
-import { bindThis } from '@/decorators.js';
-import { LegacyRateLimit } from '@/misc/rate-limit-utils.js';
-import type { IEndpointMeta } from './endpoints.js';
-
-/** @deprecated Use SkRateLimiterService instead */
-@Injectable()
-export class RateLimiterService {
-	private logger: Logger;
-	private disabled = false;
-
-	constructor(
-		@Inject(DI.redis)
-		private redisClient: Redis.Redis,
-
-		private loggerService: LoggerService,
-	) {
-		this.logger = this.loggerService.getLogger('limiter');
-
-		if (process.env.NODE_ENV !== 'production') {
-			this.disabled = true;
-		}
-	}
-
-	@bindThis
-	public limit(limitation: LegacyRateLimit & { key: NonNullable<string> }, actor: string, factor = 1) {
-		return new Promise<void>((ok, reject) => {
-			if (this.disabled) ok();
-
-			// Short-term limit
-			const minP = (): void => {
-				const minIntervalLimiter = new Limiter({
-					id: `${actor}:${limitation.key}:min`,
-					duration: limitation.minInterval! * factor,
-					max: 1,
-					db: this.redisClient,
-				});
-
-				minIntervalLimiter.get((err, info) => {
-					if (err) {
-						return reject({ code: 'ERR', info });
-					}
-
-					this.logger.debug(`${actor} ${limitation.key} min remaining: ${info.remaining}`);
-
-					if (info.remaining === 0) {
-						return reject({ code: 'BRIEF_REQUEST_INTERVAL', info });
-					} else {
-						if (hasLongTermLimit) {
-							return maxP();
-						} else {
-							return ok();
-						}
-					}
-				});
-			};
-
-			// Long term limit
-			const maxP = (): void => {
-				const limiter = new Limiter({
-					id: `${actor}:${limitation.key}`,
-					duration: limitation.duration! * factor,
-					max: limitation.max! / factor,
-					db: this.redisClient,
-				});
-
-				limiter.get((err, info) => {
-					if (err) {
-						return reject({ code: 'ERR', info });
-					}
-
-					this.logger.debug(`${actor} ${limitation.key} max remaining: ${info.remaining}`);
-
-					if (info.remaining === 0) {
-						return reject({ code: 'RATE_LIMIT_EXCEEDED', info });
-					} else {
-						return ok();
-					}
-				});
-			};
-
-			const hasShortTermLimit = typeof limitation.minInterval === 'number';
-
-			const hasLongTermLimit =
-				typeof limitation.duration === 'number' &&
-				typeof limitation.max === 'number';
-
-			if (hasShortTermLimit) {
-				minP();
-			} else if (hasLongTermLimit) {
-				maxP();
-			} else {
-				ok();
-			}
-		});
-	}
-}
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 72712bce60..7f371ea309 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -35,7 +35,8 @@ import type { FastifyReply, FastifyRequest } from 'fastify';
 // Up to 10 attempts, then 1 per minute
 const signinRateLimit: Keyed<RateLimit> = {
 	key: 'signin',
-	max: 10,
+	type: 'bucket',
+	size: 10,
 	dripRate: 1000 * 60,
 };
 
@@ -146,7 +147,7 @@ export class SigninApiService {
 
 		if (isSystemAccount(user)) {
 			return error(403, {
-				id: 's8dhsj9s-a93j-493j-ja9k-kas9sj20aml2',
+				id: 'ba4ba3bc-ef1e-4c74-ad88-1d2b7d69a100',
 			});
 		}
 
@@ -243,7 +244,7 @@ export class SigninApiService {
 				if (profile.password!.startsWith('$2')) {
 					const newHash = await argon2.hash(password);
 					this.userProfilesRepository.update(user.id, {
-						password: newHash
+						password: newHash,
 					});
 				}
 				if (!this.meta.approvalRequiredForSignup && !user.approved) this.usersRepository.update(user.id, { approved: true });
@@ -267,7 +268,7 @@ export class SigninApiService {
 				if (profile.password!.startsWith('$2')) {
 					const newHash = await argon2.hash(password);
 					this.userProfilesRepository.update(user.id, {
-						password: newHash
+						password: newHash,
 					});
 				}
 				await this.userAuthService.twoFactorAuthenticate(profile, token);
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 42137d3298..cb71047a24 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -4,7 +4,6 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { IsNull } from 'typeorm';
 import { DI } from '@/di-symbols.js';
@@ -205,7 +204,6 @@ export class SignupApiService {
 			const code = secureRndstr(16, { chars: L_CHARS });
 
 			// Generate hash of password
-			//const salt = await bcrypt.genSalt(8);
 			const hash = await argon2.hash(password);
 
 			const pendingUser = await this.userPendingsRepository.insertOne({
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index da13007bba..eaeaecb1c2 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -124,9 +124,11 @@ export class StreamingApiServerService {
 			const requestIp = proxyAddr(request, () => true );
 			const limitActor = user?.id ?? getIpHash(requestIp);
 			if (await this.rateLimitThis(limitActor, {
+				// Up to 32 connections, then 1 every 10 seconds
+				type: 'bucket',
 				key: 'wsconnect',
-				duration: ms('5min'),
-				max: 32,
+				size: 32,
+				dripRate: 10 * 1000,
 			})) {
 				socket.write('HTTP/1.1 429 Rate Limit Exceeded\r\n\r\n');
 				socket.destroy();
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
index 7b544bee8d..921ecacaf3 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
@@ -33,7 +33,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private readonly driveFilesRepository: DriveFilesRepository,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const file = await driveFilesRepository.findOneByOrFail({ id: ps.fileId });
+			const file = await this.driveFilesRepository.findOneByOrFail({ id: ps.fileId });
 			await this.moderationLogService.log(me, 'importCustomEmojis', {
 				fileName: file.name,
 			});
diff --git a/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts b/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts
index 5695866265..85e3cd0477 100644
--- a/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts
+++ b/packages/backend/src/server/api/endpoints/admin/gen-vapid-keys.ts
@@ -26,7 +26,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const keys = await generateVAPIDKeys();
-			
+
+			// TODO add moderation log
+
 			return { public: keys.publicKey, private: keys.privateKey };
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
index b99f420928..57b7170052 100644
--- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
@@ -4,7 +4,6 @@
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index e6d5dffad8..15f59907af 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -404,14 +404,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.turnstileSecretKey = ps.turnstileSecretKey;
 			}
 
-			if (ps.enableFC !== undefined) {
-				set.enableFC = ps.enableFC;
-			}
-
 			if (ps.enableTestcaptcha !== undefined) {
 				set.enableTestcaptcha = ps.enableTestcaptcha;
 			}
 
+			if (ps.enableFC !== undefined) {
+				set.enableFC = ps.enableFC;
+			}
+
 			if (ps.fcSiteKey !== undefined) {
 				set.fcSiteKey = ps.fcSiteKey;
 			}
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 8ba18a3b8d..d69850515c 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -30,7 +30,8 @@ export const meta = {
 
 	// Up to 30 calls, then 1 per 1/2 second
 	limit: {
-		max: 30,
+		type: 'bucket',
+		size: 30,
 		dripRate: 500,
 	},
 
diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
index 5217f79065..67fa5ed343 100644
--- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
+++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
@@ -16,7 +16,8 @@ export const meta = {
 	// Up to 10 calls, then 4 / second.
 	// This allows for reliable automation.
 	limit: {
-		max: 10,
+		type: 'bucket',
+		size: 10,
 		dripRate: 250,
 	},
 } as const;
diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts
index 48a2e3b40a..177bc601ac 100644
--- a/packages/backend/src/server/api/endpoints/i.ts
+++ b/packages/backend/src/server/api/endpoints/i.ts
@@ -34,7 +34,8 @@ export const meta = {
 	// up to 20 calls, then 1 per second.
 	// This handles bursty traffic when all tabs reload as a group
 	limit: {
-		max: 20,
+		type: 'bucket',
+		size: 20,
 		dripSize: 1,
 		dripRate: 1000,
 	},
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 370d9915a3..6d1972456d 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
index 893ea30391..77f71ce5fd 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
index d27c14c69b..6fde3a90a7 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import * as OTPAuth from 'otpauth';
 import * as QRCode from 'qrcode';
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
index b01e452056..d4098458d7 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
index 2fe4fdc4c0..fc5a51f81b 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts
index 4a41c7b984..a9f631cfaf 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { Endpoint } from '@/server/api/endpoint-base.js';
diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts
index 4069683740..ea84ef24d7 100644
--- a/packages/backend/src/server/api/endpoints/i/change-password.ts
+++ b/packages/backend/src/server/api/endpoints/i/change-password.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
@@ -65,7 +64,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// Generate hash of password
-			//const salt = await bcrypt.genSalt(8);
 			const hash = await argon2.hash(ps.newPassword);
 
 			await this.userProfilesRepository.update(me.id, {
diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts
index 10fb923d4f..8a2b523449 100644
--- a/packages/backend/src/server/api/endpoints/i/delete-account.ts
+++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index c7599aada2..4fd6202604 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index 0be8bfb695..dc07556760 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -5,7 +5,6 @@
 
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { MiMeta, UserProfilesRepository } from '@/models/_.js';
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index efecf0b3c1..545ec0f6eb 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -76,11 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.gtlDisabled);
 			}
 
-			const [
-				followings,
-			] = me ? await Promise.all([
-				this.cacheService.userFollowingsCache.fetch(me.id),
-			]) : [undefined];
+			const followings = me ? await this.cacheService.userFollowingsCache.fetch(me.id) : {};
 
 			//#region Construct query
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index 50711bc2bd..af9bc3b426 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -100,11 +100,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (me) this.queryService.generateMutedUserQueryForNotes(query, me);
 			if (me) this.queryService.generateBlockedUserQueryForNotes(query, me);
 
-			const [
-				followings,
-			] = me ? await Promise.all([
-				this.cacheService.userFollowingsCache.fetch(me.id),
-			]) : [undefined];
+			const followings = me ? await this.cacheService.userFollowingsCache.fetch(me.id) : {};
 
 			try {
 				if (ps.tag) {
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 58932bd83a..f2a927f3c5 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -66,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				renoteId: note.id,
 			});
 
+			// TODO inline this into the above query
 			for (const note of renotes) {
 				if (ps.quote) {
 					if (note.text) this.noteDeleteService.delete(await this.usersRepository.findOneByOrFail({ id: me.id }), note, false);
diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts
index d9240dec5e..ba0c60f4ec 100644
--- a/packages/backend/src/server/api/endpoints/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/reset-password.ts
@@ -3,7 +3,6 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-//import bcrypt from 'bcryptjs';
 import * as argon2 from 'argon2';
 import { Inject, Injectable } from '@nestjs/common';
 import type { UserProfilesRepository, PasswordResetRequestsRepository } from '@/models/_.js';
@@ -60,7 +59,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// Generate hash of password
-			//const salt = await bcrypt.genSalt(8);
 			const hash = await argon2.hash(ps.password);
 
 			await this.userProfilesRepository.update(req.userId, {
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 528de76707..33ef48226d 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -66,7 +66,8 @@ export const meta = {
 
 	// 24 calls, then 7 per second-ish (1 for each type of server info graph)
 	limit: {
-		max: 24,
+		type: 'bucket',
+		size: 24,
 		dripSize: 7,
 		dripRate: 900,
 	},
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 118362149d..7b1c8adfb8 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -59,7 +59,8 @@ export const meta = {
 
 	// up to 50 calls @ 4 per second
 	limit: {
-		max: 50,
+		type: 'bucket',
+		size: 50,
 		dripRate: 250,
 	},
 } as const;
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index 1ed2e5e9a5..e0535a2f14 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -23,7 +23,6 @@ import type Channel from './channel.js';
 
 const MAX_CHANNELS_PER_CONNECTION = 32;
 const MAX_SUBSCRIPTIONS_PER_CONNECTION = 512;
-const MAX_CACHED_NOTES_PER_CONNECTION = 64;
 
 /**
  * Main stream connection
diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
index 87c09abaf4..b7e09633ed 100644
--- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts
+++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
@@ -6,9 +6,6 @@
 import querystring from 'querystring';
 import { Inject, Injectable } from '@nestjs/common';
 import { v4 as uuid } from 'uuid';
-/* import { kinds } from '@/misc/api-permissions.js';
-import type { Config } from '@/config.js';
-import { DI } from '@/di-symbols.js'; */
 import multer from 'fastify-multer';
 import { bindThis } from '@/decorators.js';
 import type { Config } from '@/config.js';
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 165e4f3f73..99cc922281 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -427,7 +427,7 @@ export class ClientServerService {
 		fastify.get('/robots.txt', async (request, reply) => {
 			if (this.meta.robotsTxt) {
 				reply.header('Content-Type', 'text/plain');
-				return await reply.send(this.meta.robotsTxt);
+				return reply.send(this.meta.robotsTxt);
 			} else {
 				return await reply.sendFile('/robots.txt', staticAssets);
 			}
diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue
index bf96c557ea..666cbde72d 100644
--- a/packages/frontend-embed/src/components/EmNote.vue
+++ b/packages/frontend-embed/src/components/EmNote.vue
@@ -155,7 +155,6 @@ const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value
 const isLong = shouldCollapsed(appearNote.value, []);
 const collapsed = ref(appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
-
 const mergedCW = computed(() => computeMergedCw(appearNote.value));
 </script>
 
diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue
index 0961b36e35..9f4be8c666 100644
--- a/packages/frontend-embed/src/components/EmNoteDetailed.vue
+++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue
@@ -176,7 +176,6 @@ const isDeleted = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
 const isLong = shouldCollapsed(appearNote.value, []);
 const collapsed = ref(appearNote.value.cw == null && isLong);
-
 const mergedCW = computed(() => computeMergedCw(appearNote.value));
 </script>
 
diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue
index 688758edb6..a1dee733c7 100644
--- a/packages/frontend-embed/src/components/EmNoteSimple.vue
+++ b/packages/frontend-embed/src/components/EmNoteSimple.vue
@@ -36,7 +36,6 @@ const props = defineProps<{
 }>();
 
 const showContent = ref(false);
-
 const mergedCW = computed(() => computeMergedCw(props.note));
 </script>
 
diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue
index 629f0bffcd..931e1e2d79 100644
--- a/packages/frontend-embed/src/components/EmNoteSub.vue
+++ b/packages/frontend-embed/src/components/EmNoteSub.vue
@@ -55,7 +55,6 @@ const props = withDefaults(defineProps<{
 
 const showContent = ref(false);
 const replies = ref<Misskey.entities.Note[]>([]);
-
 const mergedCW = computed(() => computeMergedCw(props.note));
 
 if (props.detail) {
diff --git a/packages/frontend/src/components/DynamicNote.vue b/packages/frontend/src/components/DynamicNote.vue
index 6ce64d8352..e86fbf7374 100644
--- a/packages/frontend/src/components/DynamicNote.vue
+++ b/packages/frontend/src/components/DynamicNote.vue
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script setup lang="ts">
 import * as Misskey from 'misskey-js';
-import { computed, defineAsyncComponent, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, useTemplateRef } from 'vue';
 import type { ComponentExposed } from 'vue-component-type-helpers';
 import type MkNote from '@/components/MkNote.vue';
 import type SkNote from '@/components/SkNote.vue';
@@ -31,7 +31,7 @@ const XNote = computed(() =>
 	),
 );
 
-const rootEl = shallowRef<ComponentExposed<typeof MkNote | typeof SkNote>>();
+const rootEl = useTemplateRef<ComponentExposed<typeof MkNote | typeof SkNote>>('rootEl');
 
 defineExpose({ rootEl });
 
diff --git a/packages/frontend/src/components/DynamicNoteDetailed.vue b/packages/frontend/src/components/DynamicNoteDetailed.vue
index a6e8249f66..996137a7b0 100644
--- a/packages/frontend/src/components/DynamicNoteDetailed.vue
+++ b/packages/frontend/src/components/DynamicNoteDetailed.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script setup lang="ts">
 import * as Misskey from 'misskey-js';
-import { computed, defineAsyncComponent, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, useTemplateRef } from 'vue';
 import type { ComponentExposed } from 'vue-component-type-helpers';
 import type MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import type SkNoteDetailed from '@/components/SkNoteDetailed.vue';
@@ -28,7 +28,7 @@ const XNoteDetailed = computed(() =>
 	),
 );
 
-const rootEl = shallowRef<ComponentExposed<typeof MkNoteDetailed | typeof SkNoteDetailed>>();
+const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteDetailed | typeof SkNoteDetailed>>('rootEl');
 
 defineExpose({ rootEl });
 
diff --git a/packages/frontend/src/components/DynamicNoteSimple.vue b/packages/frontend/src/components/DynamicNoteSimple.vue
index a7713afad1..e9d31d04b7 100644
--- a/packages/frontend/src/components/DynamicNoteSimple.vue
+++ b/packages/frontend/src/components/DynamicNoteSimple.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script setup lang="ts">
 import * as Misskey from 'misskey-js';
-import { computed, defineAsyncComponent, shallowRef } from 'vue';
+import { computed, defineAsyncComponent, useTemplateRef } from 'vue';
 import type { ComponentExposed } from 'vue-component-type-helpers';
 import type MkNoteSimple from '@/components/MkNoteSimple.vue';
 import type SkNoteSimple from '@/components/SkNoteSimple.vue';
@@ -29,7 +29,7 @@ const XNoteSimple = computed(() =>
 	),
 );
 
-const rootEl = shallowRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>();
+const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>('rootEl');
 
 defineExpose({ rootEl });
 
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index b7aceb3570..21f604aa43 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -98,6 +98,7 @@ const src = computed(() => {
 		case 'fc': return 'https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.18/widget.min.js';
 		case 'mcaptcha': return null;
 		case 'testcaptcha': return null;
+		default: return null;
 	}
 });
 
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index b71cabd5ff..1282a8fedb 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -135,7 +135,9 @@ function waitForDecode() {
 			.then(() => img.value?.decode())
 			.then(() => {
 				loaded.value = true;
-			});
+			})
+			// Ignore decoding errors
+			.catch(() => {});
 	} else {
 		loaded.value = false;
 	}
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 3550d3a07b..4f639e7566 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -805,8 +805,8 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
-		os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
+		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
@@ -815,8 +815,8 @@ function showMenu(): void {
 		return;
 	}
 
-	const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
-	os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
+	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 async function menuVersions(): Promise<void> {
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index a1ae156bbe..3f1e29a824 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -749,14 +749,14 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
-		os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
+		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
 function showMenu(): void {
-	const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
-	os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
+	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 async function menuVersions(): Promise<void> {
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 42b61b841a..8cfb7338e5 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -54,11 +54,9 @@ const props = defineProps<{
 
 const menuVersionsButton = shallowRef<HTMLElement>();
 
-async function menuVersions(viaKeyboard = false): Promise<void> {
-	const { menu, cleanup } = await getNoteVersionsMenu({ note: props.note, menuVersionsButton });
-	popupMenu(menu, menuVersionsButton.value, {
-		viaKeyboard,
-	}).then(focus).finally(cleanup);
+async function menuVersions(): Promise<void> {
+	const { menu, cleanup } = await getNoteVersionsMenu({ note: props.note, menuButton: menuVersionsButton });
+	popupMenu(menu, menuVersionsButton.value).then(focus).finally(cleanup);
 }
 
 const mock = inject(DI.mock, false);
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 154efd7917..f946e6768d 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -46,7 +46,7 @@ const props = defineProps<{
 	hideFiles?: boolean;
 }>();
 
-let showContent = ref(prefer.s.uncollapseCW);
+const showContent = ref(prefer.s.uncollapseCW);
 const isDeleted = ref(false);
 
 const mergedCW = computed(() => computeMergedCw(props.note));
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index 0f334483b0..ebf30824a0 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -129,7 +129,7 @@ const props = withDefaults(defineProps<{
 const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i?.id);
 
 const el = shallowRef<HTMLElement>();
-const muted = ref($i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
+const muted = computed(() => $i ? checkWordMute(props.note, $i, $i.mutedWords) : false);
 const translation = ref<any>(null);
 const translating = ref(false);
 const isDeleted = ref(false);
@@ -142,7 +142,7 @@ const likeButton = shallowRef<HTMLElement>();
 
 const renoteTooltip = computeRenoteTooltip(renoted);
 
-let appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
+const appearNote = computed(() => isRenote ? props.note.renote as Misskey.entities.Note : props.note);
 const defaultLike = computed(() => prefer.s.like ? prefer.s.like : null);
 const replies = ref<Misskey.entities.Note[]>([]);
 
@@ -377,8 +377,8 @@ function quote() {
 }
 
 function menu(): void {
-	const { popupMenu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
-	os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
+	const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
+	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 if (props.detail) {
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index ab3947adfb..d3e49255a7 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -219,9 +219,22 @@ const props = withDefaults(defineProps<{
 	full: false,
 });
 
-const userDetailed: Ref<UserDetailed | null> = ref(null);
+type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
+
+const exportEntityName = {
+	antenna: i18n.ts.antennas,
+	blocking: i18n.ts.blockedUsers,
+	clip: i18n.ts.clips,
+	customEmoji: i18n.ts.customEmojis,
+	favorite: i18n.ts.favorites,
+	following: i18n.ts.following,
+	muting: i18n.ts.mutedUsers,
+	note: i18n.ts.notes,
+	userList: i18n.ts.lists,
+} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
 
 const followRequestDone = ref(true);
+const userDetailed: Ref<UserDetailed | null> = ref(null);
 
 // watch() is required because computed() doesn't support async.
 watch(props, async () => {
@@ -241,20 +254,6 @@ watch(props, async () => {
 	}
 }, { immediate: true });
 
-type ExportCompletedNotification = Misskey.entities.Notification & { type: 'exportCompleted' };
-
-const exportEntityName = {
-	antenna: i18n.ts.antennas,
-	blocking: i18n.ts.blockedUsers,
-	clip: i18n.ts.clips,
-	customEmoji: i18n.ts.customEmojis,
-	favorite: i18n.ts.favorites,
-	following: i18n.ts.following,
-	muting: i18n.ts.mutedUsers,
-	note: i18n.ts.notes,
-	userList: i18n.ts.lists,
-} as const satisfies Record<ExportCompletedNotification['exportedEntity'], string>;
-
 const acceptFollowRequest = () => {
 	if (!('user' in props.notification)) return;
 	followRequestDone.value = true;
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index 7292b28f25..a6f2e1b4e9 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -62,7 +62,6 @@ onUnmounted(() => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			//background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 256c84f9b6..8ac07c012a 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -110,7 +110,6 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 			left: 0;
 			width: 100%;
 			height: 64px;
-			// background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0));
 
 			> .fadeLabel {
 				display: inline-block;
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index c146099e3e..0e3a908ce0 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -805,8 +805,8 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
-		os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
+		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
@@ -815,8 +815,8 @@ function showMenu(): void {
 		return;
 	}
 
-	const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
-	os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
+	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 async function menuVersions(): Promise<void> {
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index 3e49321a3a..0bd3a078c7 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -755,14 +755,14 @@ function onContextmenu(ev: MouseEvent): void {
 		ev.preventDefault();
 		react();
 	} else {
-		const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
-		os.contextMenu(popupMenu, ev).then(focus).finally(cleanup);
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
+		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
 function showMenu(): void {
-	const { popupMenu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
-	os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
+	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 async function menuVersions(): Promise<void> {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index 7995d28f71..c5c8336a4f 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -391,8 +391,8 @@ function quote() {
 }
 
 function menu(): void {
-	const { popupMenu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
-	os.popupMenu(popupMenu, menuButton.value).then(focus).finally(cleanup);
+	const { menu, cleanup } = getNoteMenu({ note: props.note, translating, translation, isDeleted });
+	os.popupMenu(menu, menuButton.value).then(focus).finally(cleanup);
 }
 
 if (props.detail) {
diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue
index d5c80aa5f8..ef3524fe7a 100644
--- a/packages/frontend/src/components/page/page.text.vue
+++ b/packages/frontend/src/components/page/page.text.vue
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent } from 'vue';
+import { defineAsyncComponent, computed } from 'vue';
 import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import { extractUrlFromMfm } from '@/utility/extract-url-from-mfm.js';
@@ -26,7 +26,10 @@ const props = defineProps<{
 	page: Misskey.entities.Page,
 }>();
 
-const urls = props.block.text ? extractUrlFromMfm(mfm.parse(props.block.text)) : [];
+const urls = computed(() => {
+	if (!props.block.text) return [];
+	return extractUrlFromMfm(mfm.parse(props.block.text));
+});
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index ea27f7d90a..675f1eb809 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -11,6 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<template #label>{{ i18n.ts.host }}</template>
 		</MkInput>
 		<FormSplit style="margin-top: var(--MI-margin);">
+			<!-- TODO translate -->
 			<MkSelect v-model="state">
 				<template #label>{{ i18n.ts.state }}</template>
 				<option value="all">{{ i18n.ts.all }}</option>
@@ -61,7 +62,7 @@ import type { Paging } from '@/components/MkPagination.vue';
 import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import FormSplit from '@/components/form/split.vue';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { $i } from '@/i';
 
 const host = ref('');
 const state = ref('federating');
diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue
index 12df748f3d..241ee3c388 100644
--- a/packages/frontend/src/pages/about.overview.vue
+++ b/packages/frontend/src/pages/about.overview.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div class="_gaps_m">
 	<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
 		<div style="overflow: clip;">
-			<img :src="instance.sidebarLogoUrl ?? instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
+			<img :src="instance.sidebarLogoUrl ?? instance.iconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
 			<div :class="$style.bannerName">
 				<b>{{ instance.name ?? host }}</b>
 			</div>
@@ -22,6 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<FormSection>
 		<div class="_gaps_m">
 			<MkKeyValue :copy="version">
+				<!-- TODO translate -->
 				<template #key>Sharkey</template>
 				<template #value>{{ version }}</template>
 			</MkKeyValue>
@@ -101,7 +102,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</FormSection>
 
 	<FormSuspense v-slot="{ result: stats }" :p="initStats">
-		<FormSection>
+		<FormSection v-if="stats">
 			<template #label>{{ i18n.ts.statistics }}</template>
 			<FormSplit>
 				<MkKeyValue>
@@ -116,12 +117,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</FormSection>
 	</FormSuspense>
 
-	<FormSection v-if="sponsors[0].length > 0">
+	<FormSection v-if="sponsors.length > 0">
 		<template #label>Our lovely Sponsors</template>
 		<div :class="$style.contributors">
 			<span
-				v-for="sponsor in sponsors[0]"
-				:key="sponsor"
+				v-for="(sponsor, i) of sponsors"
+				:key="i"
 				style="margin-bottom: 0.5rem;"
 			>
 				<a :href="sponsor.website || sponsor.profile" target="_blank" :class="$style.contributor">
@@ -147,7 +148,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import sanitizeHtml from '@/utility/sanitize-html.js';
 import { host, version } from '@@/js/config.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
@@ -160,11 +160,12 @@ import FormSuspense from '@/components/form/suspense.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkLink from '@/components/MkLink.vue';
+import sanitizeHtml from '@/utility/sanitize-html.js';
 
-const sponsors = ref([]);
+const sponsors = ref<{ name: string, image: string | null, website: string | null, profile: string }[]>([]);
 
 const initStats = () => misskeyApi('stats', {});
-await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.push(res.sponsor_data));
+await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value = res.sponsor_data);
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index d5f9f0073b..23259a5049 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -108,6 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<template #icon><i class="ti ti-password"></i></template>
 							<template #label>IP</template>
 							<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
+							<!-- TODO translate -->
 							<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
 							<template v-if="iAmAdmin && ips">
 								<div v-for="record in ips" :key="record.ip" class="_monospace" :class="$style.ip" style="margin: 1em 0;">
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index d6be7a5cf4..a71c8f435d 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -28,6 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 					<FormLink to="/admin/server-rules">{{ i18n.ts.serverRules }}</FormLink>
 
+					<!-- TODO translate -->
 					<MkFolder v-if="bubbleTimelineEnabled">
 						<template #icon><i class="ph-drop ph-bold ph-lg"></i></template>
 						<template #label>Bubble timeline</template>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 6038a1237f..b8527a309d 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							</MkSwitch>
 						</MkFolder>
 
+						<!-- TODO translate -->
 						<MkFolder v-if="matchQuery([i18n.ts._role._options.btlAvailable, 'btlAvailable'])">
 							<template #label>{{ i18n.ts._role._options.btlAvailable }}</template>
 							<template #suffix>{{ policies.btlAvailable ? i18n.ts.yes : i18n.ts.no }}</template>
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index b9f1cbf38e..6c95d37296 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -26,11 +26,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import MkPagination from '@/components/MkPagination.vue';
+import DynamicNote from '@/components/DynamicNote.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import { i18n } from '@/i18n.js';
 import { definePage } from '@/page.js';
 import { infoImageUrl } from '@/instance.js';
-import DynamicNote from '@/components/DynamicNote.vue';
 
 const pagination = {
 	endpoint: 'i/favorites' as const,
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index ab9243005c..ff4e9c7514 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -52,6 +52,7 @@ import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { host } from '@@/js/config.js';
 import type { Paging } from '@/components/MkPagination.vue';
+import DynamicNoteDetailed from '@/components/DynamicNoteDetailed.vue';
 import MkNotes from '@/components/MkNotes.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -66,7 +67,6 @@ import { pleaseLogin } from '@/utility/please-login.js';
 import { getAppearNote } from '@/utility/get-appear-note.js';
 import { serverContext, assertServerContext } from '@/server-context.js';
 import { $i } from '@/i.js';
-import DynamicNoteDetailed from '@/components/DynamicNoteDetailed.vue';
 
 // contextは非ログイン状態の情報しかないためログイン時は利用できない
 const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index a65a1e6e71..085ba6ffde 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -48,7 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { computed, ref, shallowRef, watch } from 'vue';
+import { computed, ref, useTemplateRef, watch } from 'vue';
 import type { StyleValue } from 'vue';
 import tinycolor from 'tinycolor2';
 import * as Misskey from 'misskey-js';
@@ -61,10 +61,9 @@ import { i18n } from '@/i18n.js';
 import bytes from '@/filters/bytes.js';
 import { definePage } from '@/page.js';
 import MkSelect from '@/components/MkSelect.vue';
-import { getDriveFileMenu } from '@/utility/get-drive-file-menu.js';
 import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
 
-const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>();
+const paginationComponent = useTemplateRef<InstanceType<typeof MkPagination>>('paginationComponent');
 
 const sortMode = ref('+size');
 const pagination = {
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue
index 1b364bdba3..c03dff8c32 100644
--- a/packages/frontend/src/pages/settings/preferences.vue
+++ b/packages/frontend/src/pages/settings/preferences.vue
@@ -861,7 +861,6 @@ import MkFeatureBanner from '@/components/MkFeatureBanner.vue';
 import { globalEvents } from '@/events.js';
 import { claimAchievement } from '@/utility/achievements.js';
 import { instance } from '@/instance.js';
-import Search from '@/pages/search.vue';
 
 // Sharkey imports
 import { searchEngineMap } from '@/utility/search-engine-map.js';
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index cbb0889acf..ee26a8911e 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -187,7 +187,6 @@ import { claimAchievement } from '@/utility/achievements.js';
 import { store } from '@/store.js';
 import MkInfo from '@/components/MkInfo.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
-import { globalEvents } from '@/events.js';
 
 const $i = ensureSignin();
 
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index da14f180d5..4d9c0bb742 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -8,6 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div :class="$style.formContainer">
 		<form :class="$style.form" class="_panel" @submit.prevent="submit()">
 			<div :class="$style.title">
+				<!-- TODO translate -->
 				<div>Welcome to Sharkey!</div>
 				<div :class="$style.version">v{{ version }}</div>
 			</div>
diff --git a/packages/frontend/src/types/post-form.ts b/packages/frontend/src/types/post-form.ts
index 94958b6623..3d7c2ba35c 100644
--- a/packages/frontend/src/types/post-form.ts
+++ b/packages/frontend/src/types/post-form.ts
@@ -17,6 +17,7 @@ export interface PostFormProps {
 	initialFiles?: Misskey.entities.DriveFile[];
 	initialLocalOnly?: boolean;
 	initialVisibleUsers?: Misskey.entities.UserDetailed[];
+	/* TODO inline this into the entity */
 	initialNote?: Misskey.entities.Note & {
 		isSchedule?: boolean,
 	};
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index 4335b00ef0..a8b3e9c31e 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -31,11 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:enterFromClass="$style.transition_notification_enterFrom"
 	:leaveToClass="$style.transition_notification_leaveTo"
 >
-	<div
-		v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{
-			pointerEvents: getPointerEvents()
-		}"
-	>
+	<div v-for="notification in notifications" :key="notification.id" :class="$style.notification" :style="{ pointerEvents: getPointerEvents() }">
 		<XNotification :notification="notification"/>
 	</div>
 </component>
@@ -67,10 +63,9 @@ import { prefer } from '@/preferences.js';
 import { globalEvents } from '@/events.js';
 import { store } from '@/store.js';
 
-const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
-
 const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
 const XUpload = defineAsyncComponent(() => import('./upload.vue'));
+const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
 
 const dev = _DEV_;
 
@@ -99,7 +94,6 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
 if ($i) {
 	const connection = useStream().useChannel('main', null, 'UI');
 	connection.on('notification', onNotification);
-
 	globalEvents.on('clientNotification', notification => onNotification(notification, true));
 
 	//#region Listen message from SW
@@ -132,8 +126,8 @@ function getPointerEvents() {
 	position: fixed;
 	z-index: 3900000;
 	padding: 0 var(--MI-margin);
-	display: flex;
 	pointer-events: none;
+	display: flex;
 
 	&.notificationsPosition_leftTop {
 		top: var(--MI-margin);
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 1948d01076..a579a2c012 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.top">
 			<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
 			<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
-				<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
+				<img :src="instance.sidebarLogoUrl && !iconOnly ? instance.sidebarLogoUrl : instance.iconUrl || '/favicon.ico'" alt="" :class="instance.sidebarLogoUrl && !iconOnly ? $style.wideInstanceIcon : $style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
 			</button>
 		</div>
 		<div :class="$style.middle">
diff --git a/packages/frontend/src/use/use-note-capture.ts b/packages/frontend/src/use/use-note-capture.ts
index 54d0e128e1..58421dc596 100644
--- a/packages/frontend/src/use/use-note-capture.ts
+++ b/packages/frontend/src/use/use-note-capture.ts
@@ -4,8 +4,8 @@
  */
 
 import { onUnmounted } from 'vue';
-import * as Misskey from 'misskey-js';
 import type { Ref, ShallowRef } from 'vue';
+import * as Misskey from 'misskey-js';
 import { useStream } from '@/stream.js';
 import { $i } from '@/i.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
diff --git a/packages/frontend/src/utility/deep-equal.ts b/packages/frontend/src/utility/deep-equal.ts
index 09d2ff289e..2859641dc7 100644
--- a/packages/frontend/src/utility/deep-equal.ts
+++ b/packages/frontend/src/utility/deep-equal.ts
@@ -3,7 +3,7 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike | undefined } | Map<string, JsonLike>;
+type JsonLike = string | number | boolean | null | undefined | JsonLike[] | { [key: string]: JsonLike } | Map<string, JsonLike>;
 
 export function deepEqual(a: JsonLike, b: JsonLike): boolean {
 	if (a === b) return true;
diff --git a/packages/frontend/src/utility/favicon-dot.ts b/packages/frontend/src/utility/favicon-dot.ts
index a903b4cc7f..5ee02963a7 100644
--- a/packages/frontend/src/utility/favicon-dot.ts
+++ b/packages/frontend/src/utility/favicon-dot.ts
@@ -14,20 +14,18 @@ class FavIconDot {
 	private hasLoaded: Promise<void> | undefined;
 
 	constructor() {
-		this.canvas = document.createElement('canvas');
+		this.canvas = window.document.createElement('canvas');
 	}
 
 	/**
 	 * Must be called before calling any other functions
 	 */
 	public async setup() {
-		const element: HTMLLinkElement = await this.getOrMakeFaviconElement();
-
-		this.faviconEL = element;
+		this.faviconEL = await this.getOrMakeFaviconElement();
 		this.src = this.faviconEL.getAttribute('href');
 		this.ctx = this.canvas.getContext('2d');
 
-		this.faviconImage = document.createElement('img');
+		this.faviconImage = window.document.createElement('img');
 		this.faviconImage.crossOrigin = 'anonymous';
 
 		this.hasLoaded = new Promise((resolve, reject) => {
@@ -46,7 +44,7 @@ class FavIconDot {
 
 	private async getOrMakeFaviconElement(): Promise<HTMLLinkElement> {
 		return new Promise((resolve, reject) => {
-			const favicon = (document.querySelector('link[rel=icon]') ?? this.createFaviconElem()) as HTMLLinkElement;
+			const favicon = (window.document.querySelector('link[rel=icon]') ?? this.createFaviconElem()) as HTMLLinkElement;
 			favicon.addEventListener('load', () => {
 				resolve(favicon);
 			});
@@ -59,12 +57,12 @@ class FavIconDot {
 	}
 
 	private createFaviconElem() {
-		const newLink = document.createElement('link');
+		const newLink = window.document.createElement('link');
 		newLink.setAttribute('rel', 'icon');
 		newLink.setAttribute('href', '/favicon.ico');
 		newLink.setAttribute('type', 'image/x-icon');
 
-		document.head.appendChild(newLink);
+		window.document.head.appendChild(newLink);
 		return newLink;
 	}
 
@@ -79,7 +77,7 @@ class FavIconDot {
 		this.ctx.beginPath();
 		const radius = Math.min(this.faviconImage.width, this.faviconImage.height) * 0.2;
 		this.ctx.arc(this.faviconImage.width - radius, radius, radius, 0, 2 * Math.PI);
-		const computedStyle = getComputedStyle(document.documentElement);
+		const computedStyle = getComputedStyle(window.document.documentElement);
 		this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--MI_THEME-navIndicator')).toHexString();
 		this.ctx.strokeStyle = 'white';
 		this.ctx.fill();
@@ -111,7 +109,7 @@ class FavIconDot {
 	public async worksOnInstance() {
 		try {
 			await this.setVisible(true);
-			await new Promise((resolve) => setTimeout(resolve, 1000));
+			await new Promise((resolve) => window.setTimeout(resolve, 1000));
 			await this.setVisible(false);
 		} catch (error) {
 			console.error('error setting notification dot', error);
@@ -138,7 +136,7 @@ export async function setFavIconDot(visible: boolean) {
 	};
 
 	// If document is already loaded, set visibility immediately
-	if (document.readyState === 'complete') {
+	if (window.document.readyState === 'complete') {
 		await setIconVisibility();
 	} else {
 		// Otherwise, set visibility when window loads
diff --git a/packages/frontend/src/utility/get-note-menu.ts b/packages/frontend/src/utility/get-note-menu.ts
index 3e09c74647..723b109547 100644
--- a/packages/frontend/src/utility/get-note-menu.ts
+++ b/packages/frontend/src/utility/get-note-menu.ts
@@ -541,7 +541,7 @@ export function getNoteMenu(props: {
 	};
 
 	return {
-		popupMenu: menuItems,
+		menu: menuItems,
 		cleanup,
 	};
 }
diff --git a/packages/frontend/test/aiscript/ui.test.ts b/packages/frontend/test/aiscript/ui.test.ts
index 4cd07724bb..3102e22e81 100644
--- a/packages/frontend/test/aiscript/ui.test.ts
+++ b/packages/frontend/test/aiscript/ui.test.ts
@@ -25,7 +25,7 @@ type ExeResult = {
 	root: AsUiRoot;
 	get: (id: string) => AsUiComponent;
 	outputs: values.Value[];
-}
+};
 
 async function exe(script: string): Promise<ExeResult> {
 	const rootRef = ref<AsUiRoot>();
-- 
cgit v1.2.3-freya


From 7c49b633a679d83d1e43fd3f82ecae25123b5ed6 Mon Sep 17 00:00:00 2001
From: Hazelnoot <acomputerdog@gmail.com>
Date: Wed, 2 Apr 2025 21:55:08 -0400
Subject: fix vue errors from dynamic note components

---
 packages/frontend/src/components/DynamicNote.vue         | 8 +++-----
 packages/frontend/src/components/DynamicNoteDetailed.vue | 8 +++-----
 packages/frontend/src/components/DynamicNoteSimple.vue   | 8 +++-----
 3 files changed, 9 insertions(+), 15 deletions(-)

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

diff --git a/packages/frontend/src/components/DynamicNote.vue b/packages/frontend/src/components/DynamicNote.vue
index e86fbf7374..a5008e9ddf 100644
--- a/packages/frontend/src/components/DynamicNote.vue
+++ b/packages/frontend/src/components/DynamicNote.vue
@@ -24,11 +24,9 @@ import type SkNote from '@/components/SkNote.vue';
 import { prefer } from '@/preferences';
 
 const XNote = computed(() =>
-	defineAsyncComponent(() =>
-		prefer.r.noteDesign.value === 'misskey'
-			? import('@/components/MkNote.vue')
-			: import('@/components/SkNote.vue'),
-	),
+	prefer.r.noteDesign.value === 'misskey'
+		? defineAsyncComponent(() => import('@/components/MkNote.vue'))
+		: defineAsyncComponent(() => import('@/components/SkNote.vue')),
 );
 
 const rootEl = useTemplateRef<ComponentExposed<typeof MkNote | typeof SkNote>>('rootEl');
diff --git a/packages/frontend/src/components/DynamicNoteDetailed.vue b/packages/frontend/src/components/DynamicNoteDetailed.vue
index 996137a7b0..21bf00ccbe 100644
--- a/packages/frontend/src/components/DynamicNoteDetailed.vue
+++ b/packages/frontend/src/components/DynamicNoteDetailed.vue
@@ -21,11 +21,9 @@ import type SkNoteDetailed from '@/components/SkNoteDetailed.vue';
 import { prefer } from '@/preferences';
 
 const XNoteDetailed = computed(() =>
-	defineAsyncComponent(() =>
-		prefer.r.noteDesign.value === 'misskey'
-			? import('@/components/MkNoteDetailed.vue')
-			: import('@/components/SkNoteDetailed.vue'),
-	),
+	prefer.r.noteDesign.value === 'misskey'
+		? defineAsyncComponent(() => import('@/components/MkNoteDetailed.vue'))
+		: defineAsyncComponent(() => import('@/components/SkNoteDetailed.vue')),
 );
 
 const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteDetailed | typeof SkNoteDetailed>>('rootEl');
diff --git a/packages/frontend/src/components/DynamicNoteSimple.vue b/packages/frontend/src/components/DynamicNoteSimple.vue
index e9d31d04b7..7ca345ccfa 100644
--- a/packages/frontend/src/components/DynamicNoteSimple.vue
+++ b/packages/frontend/src/components/DynamicNoteSimple.vue
@@ -22,11 +22,9 @@ import type SkNoteSimple from '@/components/SkNoteSimple.vue';
 import { prefer } from '@/preferences';
 
 const XNoteSimple = computed(() =>
-	defineAsyncComponent<typeof MkNoteSimple | typeof SkNoteSimple>(() =>
-		prefer.r.noteDesign.value === 'misskey'
-			? import('@/components/MkNoteSimple.vue')
-			: import('@/components/SkNoteSimple.vue'),
-	),
+	prefer.r.noteDesign.value === 'misskey'
+		? defineAsyncComponent(() => import('@/components/MkNoteSimple.vue'))
+		: defineAsyncComponent(() => import('@/components/SkNoteSimple.vue')),
 );
 
 const rootEl = useTemplateRef<ComponentExposed<typeof MkNoteSimple | typeof SkNoteSimple>>('rootEl');
-- 
cgit v1.2.3-freya

