diff --git a/.config/ci.yml b/.config/ci.yml
index c381d21d921d9d345fe74dfee7ae8a2390fa102f..44092d36623b4ff32fec95a83473623eccac2e2f 100644
--- a/.config/ci.yml
+++ b/.config/ci.yml
@@ -106,7 +106,7 @@ redis:
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
-# You can set scope to local (default value) or global 
+# You can set scope to local (default value) or global
 # (include notes from remote).
 
 #meilisearch:
@@ -198,13 +198,18 @@ proxyRemoteFiles: true
 #   https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
 #videoThumbnailGenerator: https://example.com
 
-# Sign to ActivityPub GET request (default: true)
+# Sign outgoing ActivityPub GET request (default: true)
 signToActivityPubGet: true
+# Sign outgoing ActivityPub Activities (default: true)
+# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
+# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
+# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
+attachLdSignatureForRelays: true
 # check that inbound ActivityPub GET requests are signed ("authorized fetch")
 checkActivityPubGetSignature: false
 
 # For security reasons, uploading attachments from the intranet is prohibited,
-# but exceptions can be made from the following settings. Default value is "undefined". 
+# but exceptions can be made from the following settings. Default value is "undefined".
 # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
 #allowedPrivateNetworks: [
 #  '127.0.0.1/32'
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index c22bd83c2e0bdd3a51678365ff5377aaf9868244..f4645d672d6b3918aaeb5a138315a3b656893aef 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -270,8 +270,13 @@ proxyRemoteFiles: true
 #   https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
 #videoThumbnailGenerator: https://example.com
 
-# Sign to ActivityPub GET request (default: true)
+# Sign outgoing ActivityPub GET request (default: true)
 signToActivityPubGet: true
+# Sign outgoing ActivityPub Activities (default: true)
+# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
+# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
+# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
+attachLdSignatureForRelays: true
 # check that inbound ActivityPub GET requests are signed ("authorized fetch")
 checkActivityPubGetSignature: false
 
diff --git a/.config/example.yml b/.config/example.yml
index ae55b983bb548ed297c6ea495bca83bd2afe3063..21e85b7b8904566136155c4fbed59f6d83d6076a 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -285,8 +285,13 @@ proxyRemoteFiles: true
 #   https://example.com/thumbnail.webp?thumbnail=1&url=https%3A%2F%2Fstorage.example.com%2Fpath%2Fto%2Fvideo.mp4
 #videoThumbnailGenerator: https://example.com
 
-# Sign to ActivityPub GET request (default: true)
+# Sign outgoing ActivityPub GET request (default: true)
 signToActivityPubGet: true
+# Sign outgoing ActivityPub Activities (default: true)
+# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
+# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
+# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
+attachLdSignatureForRelays: true
 # check that inbound ActivityPub GET requests are signed ("authorized fetch")
 checkActivityPubGetSignature: false
 
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2acacd6dfa6c1f3768e278936e9d42dace6a5c83..053fffcaeb360131250f39c6fceab63b67f4b330 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -96,6 +96,15 @@ If your language is not listed in Crowdin, please open an issue.
 
 ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
 
+## Icon Font (Shark Font)
+Sharkey has its own Icon Font called Shark Font which can be found at https://activitypub.software/TransFem-org/shark-font
+Build Instructions can all be found over there in the `README`.
+
+If you have an Icon Suggestion or want to add an Icon please open an issue/merge request over at that repo.
+
+When Updating the Font make sure to copy **all generated files** from the `dest` folder into `packages/backend/assets/fonts/sharkey-icons`
+For the CSS simply copy the file content and replace the old content in `style.css` and for the WOFF, TTF and SVG simply replace them.
+
 ## Development
 During development, it is useful to use the
 
diff --git a/chart/files/default.yml b/chart/files/default.yml
index 2e1381ec574ac1c684823a6fb4d91608d3b3f79f..aab7ed6ce12edd997908bdaafb14d28b8fa30d08 100644
--- a/chart/files/default.yml
+++ b/chart/files/default.yml
@@ -208,8 +208,13 @@ id: "aidx"
 # Media Proxy
 #mediaProxy: https://example.com/proxy
 
-# Sign to ActivityPub GET request (default: true)
+# Sign outgoing ActivityPub GET request (default: true)
 signToActivityPubGet: true
+# Sign outgoing ActivityPub Activities (default: true)
+# Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
+# When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
+# This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
+attachLdSignatureForRelays: true
 # check that inbound ActivityPub GET requests are signed ("authorized fetch")
 checkActivityPubGetSignature: false
 
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 03a5a91e84e4e4e4a0954f7b3a5df8697a11763b..689dc560c08839c108d14b162c73abaeaa7205a6 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1305,8 +1305,8 @@ alwaysConfirmFollow: "Always confirm when following"
 inquiry: "Contact"
 _delivery:
   status: "Delivery status"
-  stop: "Suspended"
-  resume: "Delivery resume"
+  stop: "Suspend delivery"
+  resume: "Resume delivery"
   _type:
     none: "Publishing"
     manuallySuspended: "Manually suspended"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 2b04c5abfb0c13459d01f93eda570fd94514658d..be86420e67da967c45ec5401a47fc7c7377ed38d 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -1238,7 +1238,8 @@ keepOriginalFilename: "Mantieni il nome file originale"
 keepOriginalFilenameDescription: "Disattivandola, i file verranno caricati usando nomi casuali."
 noDescription: "Manca la descrizione"
 _delivery:
-  stop: "Sospensione"
+  stop: "Sospendi la distribuzione di attività"
+  resume: "Riprendi la distribuzione di attività"
   _type:
     none: "Pubblicazione"
 _bubbleGame:
diff --git a/package.json b/package.json
index 519a8c453d2ea307332fe1979e326af80d313c87..f60fd789dbc10774836b0d235019908c5536cc68 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
 		"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
 		"build-storybook": "pnpm --filter frontend build-storybook",
 		"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
-		"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
+		"start": "pnpm check:connect && cd packages/backend && MK_WARNED_ABOUT_CONFIG=true node ./built/boot/entry.js",
 		"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
 		"init": "pnpm migrate",
 		"migrate": "cd packages/backend && pnpm migrate",
diff --git a/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.svg b/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.svg
deleted file mode 100644
index 9d21137072a19c2da1fb41a98c9199c57c5d333c..0000000000000000000000000000000000000000
Binary files a/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.svg and /dev/null differ
diff --git a/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.ttf b/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.ttf
deleted file mode 100644
index a2601e0f1b00c4f2e72249911b911e48c55d6de1..0000000000000000000000000000000000000000
Binary files a/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.ttf and /dev/null differ
diff --git a/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.woff b/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.woff
deleted file mode 100644
index d9f471fa35a861eb745fb136094ffd9c90b7922f..0000000000000000000000000000000000000000
Binary files a/packages/backend/assets/fonts/sharkey-icons/custom-sharkey-icons.woff and /dev/null differ
diff --git a/packages/backend/assets/fonts/sharkey-icons/shark-font.svg b/packages/backend/assets/fonts/sharkey-icons/shark-font.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b67bd7f7d8119ae3bd9f3a0ed6557f1ca7a1f64a
Binary files /dev/null and b/packages/backend/assets/fonts/sharkey-icons/shark-font.svg differ
diff --git a/packages/backend/assets/fonts/sharkey-icons/shark-font.ttf b/packages/backend/assets/fonts/sharkey-icons/shark-font.ttf
new file mode 100644
index 0000000000000000000000000000000000000000..0fdf04df082cc5724a7c94b19b299cfd5a317040
Binary files /dev/null and b/packages/backend/assets/fonts/sharkey-icons/shark-font.ttf differ
diff --git a/packages/backend/assets/fonts/sharkey-icons/shark-font.woff b/packages/backend/assets/fonts/sharkey-icons/shark-font.woff
new file mode 100644
index 0000000000000000000000000000000000000000..993666bc3aacaf202da6002fbc9baa8e7a881581
Binary files /dev/null and b/packages/backend/assets/fonts/sharkey-icons/shark-font.woff differ
diff --git a/packages/backend/assets/fonts/sharkey-icons/style.css b/packages/backend/assets/fonts/sharkey-icons/style.css
index 7fb0f9450470c649b09732630360c7a0bd521c3c..7168702e5a38a4c7edeee32b4df4728b6c0568de 100644
--- a/packages/backend/assets/fonts/sharkey-icons/style.css
+++ b/packages/backend/assets/fonts/sharkey-icons/style.css
@@ -1,31 +1,114 @@
-@charset "UTF-8";
-
 @font-face {
-  font-family: "custom-sharkey-icons";
-  src: url("./custom-sharkey-icons.woff") format("woff"),
-    url("./custom-sharkey-icons.ttf") format("truetype"),
-    url("./custom-sharkey-icons.svg#custom-sharkey-icons") format("svg");
-  font-weight: normal;
+  font-display: auto;
+  font-family: "shark-font";
   font-style: normal;
-  font-display: block;
+  font-weight: normal;
+  
+  src: url("./shark-font.woff?1722899913909") format("woff"), url("./shark-font.ttf?1722899913909") format("truetype"), url("./shark-font.svg?1722899913909#shark-font") format("svg");
 }
 
 .sk-icons {
-  font-family: "custom-sharkey-icons" !important;
-  font-style: normal;
+  display: inline-block;
+  font-family: "shark-font";
   font-weight: normal;
+  font-style: normal;
   font-variant: normal;
-  text-transform: none;
+  text-rendering: auto;
   line-height: 1;
-  speak: none;
-  -webkit-font-smoothing: antialiased;
   -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+}
+
+.sk-icons-lg {
+  font-size: 1.33333em;
+  line-height: 0.75em;
+  vertical-align: -0.0667em;
+}
+
+.sk-icons-xs {
+  font-size: 0.75em;
+}
+
+.sk-icons-sm {
+  font-size: 0.875em;
+}
+
+.sk-icons-1x {
+  font-size: 1em;
+}
+
+.sk-icons-2x {
+  font-size: 2em;
+}
+
+.sk-icons-3x {
+  font-size: 3em;
+}
+
+.sk-icons-4x {
+  font-size: 4em;
 }
 
-.sk-icons.sk-shark:before {
-  content: "\61";
+.sk-icons-5x {
+  font-size: 5em;
 }
 
-.sk-icons.sk-misskey:before {
-  content: "\62";
+.sk-icons-6x {
+  font-size: 6em;
 }
+
+.sk-icons-7x {
+  font-size: 7em;
+}
+
+.sk-icons-8x {
+  font-size: 8em;
+}
+
+.sk-icons-9x {
+  font-size: 9em;
+}
+
+.sk-icons-10x {
+  font-size: 10em;
+}
+
+.sk-icons-fw {
+  text-align: center;
+  width: 1.25em;
+}
+
+.sk-icons-border {
+  border: solid 0.08em #eee;
+  border-radius: 0.1em;
+  padding: 0.2em 0.25em 0.15em;
+}
+
+.sk-icons-pull-left {
+  float: left;
+}
+
+.sk-icons-pull-right {
+  float: right;
+}
+
+.sk-icons.sk-icons-pull-left {
+  margin-right: 0.3em;
+}
+
+.sk-icons.sk-icons-pull-right {
+  margin-left: 0.3em;
+}
+
+
+.sk-icons.sk-foldermove::before {
+  content: "\ea01";
+}
+
+.sk-icons.sk-misskey::before {
+  content: "\ea02";
+}
+
+.sk-icons.sk-shark::before {
+  content: "\ea03";
+}
\ No newline at end of file
diff --git a/packages/backend/scripts/check_connect.js b/packages/backend/scripts/check_connect.js
index ba25fd416c782cd376cb0e3ba37df6b3656e422f..d4bf4baf43cbf2048eda4db33ece2f82921718ee 100644
--- a/packages/backend/scripts/check_connect.js
+++ b/packages/backend/scripts/check_connect.js
@@ -5,11 +5,33 @@
 
 import Redis from 'ioredis';
 import { loadConfig } from '../built/config.js';
+import { createPostgresDataSource } from '../built/postgres.js';
 
 const config = loadConfig();
-const redis = new Redis(config.redis);
 
-redis.on('connect', () => redis.disconnect());
-redis.on('error', (e) => {
-	throw e;
-});
+// createPostgresDataSource handels primaries and replicas automatically.
+// usually, it only opens connections first use, so we force it using
+// .initialize()
+createPostgresDataSource(config)
+	.initialize()
+	.then(c => { c.destroy() })
+	.catch(e => { throw e });
+
+
+// Connect to all redis servers
+function connectToRedis(redisOptions) {
+	const redis = new Redis(redisOptions);
+	redis.on('connect', () => redis.disconnect());
+	redis.on('error', (e) => {
+		throw e;
+	});
+}
+
+// If not all of these are defined, the default one gets reused.
+// so we use a Set to only try connecting once to each **uniq** redis.
+(new Set([
+	config.redis,
+	config.redisForPubsub,
+	config.redisForJobQueue,
+	config.redisForTimelines,
+])).forEach(connectToRedis);
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index 303ba9420764b4cc79b8c5b8df42a3cc477115fb..f757ed64b93b42445ee0fee6bbd72605bcbcff27 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -112,6 +112,11 @@ export async function masterMain() {
 			await server();
 		}
 
+		if (config.clusterLimit === 0) {
+			bootLogger.error("Configuration error: we can't create workers, `config.clusterLimit` is 0 (if you don't want to use clustering, set the environment variable `MK_DISABLE_CLUSTERING` to a non-empty value instead)", null, true);
+			process.exit(1);
+		}
+
 		await spawnWorkers(config.clusterLimit);
 	}
 
@@ -180,7 +185,10 @@ async function connectDb(): Promise<void> {
 */
 
 async function spawnWorkers(limit = 1) {
-	const workers = Math.min(limit, os.cpus().length);
+	const cpuCount = os.cpus().length;
+	// in some weird environments, node can't count the CPUs; we trust the config in those cases
+	const workers = cpuCount === 0 ? limit : Math.min(limit, cpuCount);
+
 	bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`);
 	await Promise.all([...Array(workers)].map(spawnWorker));
 	bootLogger.succ('All workers started');
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index 58c4d028aa3305283c842f70e5d3cbc124e2d52e..9411a7b599347ce91e3be742512139d18600d98b 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -12,9 +12,10 @@ import * as Sentry from '@sentry/node';
 import type { RedisOptions } from 'ioredis';
 
 type RedisOptionsSource = Partial<RedisOptions> & {
-	host: string;
-	port: number;
+	host?: string;
+	port?: number;
 	family?: number;
+	path?: string,
 	pass: string;
 	db?: number;
 	prefix?: string;
@@ -95,6 +96,7 @@ type Source = {
 	customMOTD?: string[];
 
 	signToActivityPubGet?: boolean;
+	attachLdSignatureForRelays?: boolean;
 	checkActivityPubGetSignature?: boolean;
 
 	perChannelMaxNoteCacheCount?: number;
@@ -161,6 +163,7 @@ export type Config = {
 	proxyRemoteFiles: boolean | undefined;
 	customMOTD: string[] | undefined;
 	signToActivityPubGet: boolean;
+	attachLdSignatureForRelays: boolean;
 	checkActivityPubGetSignature: boolean | undefined;
 
 	version: string;
@@ -221,8 +224,15 @@ export function loadConfig(): Config {
 		JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
 		: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
 
-	const config = globSync(path).sort()
-		.map(path => fs.readFileSync(path, 'utf-8'))
+	const configFiles = globSync(path).sort();
+
+	if (configFiles.length === 0
+			&& !process.env['MK_WARNED_ABOUT_CONFIG']) {
+		console.log('No config files loaded, check if this is intentional');
+		process.env['MK_WARNED_ABOUT_CONFIG'] = '1';
+	}
+
+	const config = configFiles.map(path => fs.readFileSync(path, 'utf-8'))
 		.map(contents => yaml.load(contents) as Source)
 		.reduce(
 			(acc: Source, cur: Source) => Object.assign(acc, cur),
@@ -248,7 +258,7 @@ export function loadConfig(): Config {
 		version,
 		publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
 		url: url.origin,
-		port: config.port ?? parseInt(process.env.PORT ?? '', 10),
+		port: config.port ?? parseInt(process.env.PORT ?? '3000', 10),
 		socket: config.socket,
 		chmodSocket: config.chmodSocket,
 		disableHsts: config.disableHsts,
@@ -291,6 +301,7 @@ export function loadConfig(): Config {
 		proxyRemoteFiles: config.proxyRemoteFiles,
 		customMOTD: config.customMOTD,
 		signToActivityPubGet: config.signToActivityPubGet ?? true,
+		attachLdSignatureForRelays: config.attachLdSignatureForRelays ?? true,
 		checkActivityPubGetSignature: config.checkActivityPubGetSignature,
 		mediaProxy: externalMediaProxy ?? internalMediaProxy,
 		externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
@@ -436,8 +447,8 @@ function applyEnvOverrides(config: Source) {
 
 	// these are all the settings that can be overridden
 
-	_apply_top([['url', 'port', 'socket', 'chmodSocket', 'disableHsts']]);
-	_apply_top(['db', ['host', 'port', 'db', 'user', 'pass']]);
+	_apply_top([['url', 'port', 'socket', 'chmodSocket', 'disableHsts', 'id', 'dbReplications']]);
+	_apply_top(['db', ['host', 'port', 'db', 'user', 'pass', 'disableCache']]);
 	_apply_top(['dbSlaves', Array.from((config.dbSlaves ?? []).keys()), ['host', 'port', 'db', 'user', 'pass']]);
 	_apply_top([
 		['redis', 'redisForPubsub', 'redisForJobQueue', 'redisForTimelines'],
@@ -447,7 +458,8 @@ function applyEnvOverrides(config: Source) {
 	_apply_top([['sentryForFrontend', 'sentryForBackend'], 'options', ['dsn', 'profileSampleRate', 'serverName', 'includeLocalVariables', 'proxy', 'keepAlive', 'caCerts']]);
 	_apply_top(['sentryForBackend', 'enableNodeProfiling']);
 	_apply_top([['clusterLimit', 'deliverJobConcurrency', 'inboxJobConcurrency', 'relashionshipJobConcurrency', 'deliverJobPerSec', 'inboxJobPerSec', 'relashionshipJobPerSec', 'deliverJobMaxAttempts', 'inboxJobMaxAttempts']]);
-	_apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'videoThumbnailGenerator']]);
+	_apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]);
 	_apply_top([['maxFileSize', 'maxNoteLength', 'pidFile']]);
 	_apply_top(['import', ['downloadTimeout', 'maxFileSize']]);
+	_apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature']]);
 }
diff --git a/packages/backend/src/core/AvatarDecorationService.ts b/packages/backend/src/core/AvatarDecorationService.ts
index 21e31d79a4292dc3a99fa90e71d00ba4c020ba10..fa3f63677eb02fc99dcd109b2c98c84505c77e9a 100644
--- a/packages/backend/src/core/AvatarDecorationService.ts
+++ b/packages/backend/src/core/AvatarDecorationService.ts
@@ -29,7 +29,7 @@ export class AvatarDecorationService implements OnApplicationShutdown {
 		private moderationLogService: ModerationLogService,
 		private globalEventService: GlobalEventService,
 	) {
-		this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30);
+		this.cache = new MemorySingleCache<MiAvatarDecoration[]>(1000 * 60 * 30); // 30s
 
 		this.redisForSub.on('message', this.onMessage);
 	}
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index d008e7ec52a3c6cd4b3fcdee28ef95d793dbb024..6725ebe75b14503583df80ad055a1455c7c91eb0 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -56,10 +56,10 @@ export class CacheService implements OnApplicationShutdown {
 	) {
 		//this.onMessage = this.onMessage.bind(this);
 
-		this.userByIdCache = new MemoryKVCache<MiUser>(Infinity);
-		this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(Infinity);
-		this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(Infinity);
-		this.uriPersonCache = new MemoryKVCache<MiUser | null>(Infinity);
+		this.userByIdCache = new MemoryKVCache<MiUser>(1000 * 60 * 5); // 5m
+		this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(1000 * 60 * 5); // 5m
+		this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 5); // 5m
+		this.uriPersonCache = new MemoryKVCache<MiUser | null>(1000 * 60 * 5); // 5m
 
 		this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', {
 			lifetime: 1000 * 60 * 30, // 30m
@@ -135,14 +135,14 @@ export class CacheService implements OnApplicationShutdown {
 					if (user == null) {
 						this.userByIdCache.delete(body.id);
 						this.localUserByIdCache.delete(body.id);
-						for (const [k, v] of this.uriPersonCache.cache.entries()) {
+						for (const [k, v] of this.uriPersonCache.entries) {
 							if (v.value?.id === body.id) {
 								this.uriPersonCache.delete(k);
 							}
 						}
 					} else {
 						this.userByIdCache.set(user.id, user);
-						for (const [k, v] of this.uriPersonCache.cache.entries()) {
+						for (const [k, v] of this.uriPersonCache.entries) {
 							if (v.value?.id === user.id) {
 								this.uriPersonCache.set(k, user);
 							}
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index bfbc2b172da65207d7e297c8e12f622c84ba1f41..aa99261549abdd0d8794277eda9c9e7fd0f4d352 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -26,7 +26,7 @@ const parseEmojiStrRegexp = /^([-\w]+)(?:@([\w.-]+))?$/;
 
 @Injectable()
 export class CustomEmojiService implements OnApplicationShutdown {
-	private cache: MemoryKVCache<MiEmoji | null>;
+	private emojisCache: MemoryKVCache<MiEmoji | null>;
 	public localEmojisCache: RedisSingleCache<Map<string, MiEmoji>>;
 
 	constructor(
@@ -49,7 +49,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 		private globalEventService: GlobalEventService,
 		private driveService: DriveService,
 	) {
-		this.cache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12);
+		this.emojisCache = new MemoryKVCache<MiEmoji | null>(1000 * 60 * 60 * 12); // 12h
 
 		this.localEmojisCache = new RedisSingleCache<Map<string, MiEmoji>>(this.redisClient, 'localEmojis', {
 			lifetime: 1000 * 60 * 30, // 30m
@@ -142,6 +142,13 @@ export class CustomEmojiService implements OnApplicationShutdown {
 
 		this.localEmojisCache.refresh();
 
+		if (data.driveFile != null) {
+			const file = await this.driveFilesRepository.findOneBy({ url: emoji.originalUrl, userHost: emoji.host ? emoji.host : IsNull() });
+			if (file && file.id != data.driveFile.id) {
+				await this.driveService.deleteFile(file, false, moderator ? moderator : undefined);
+			}
+		}
+
 		const packed = await this.emojiEntityService.packDetailed(emoji.id);
 
 		if (emoji.name === data.name) {
@@ -350,14 +357,14 @@ export class CustomEmojiService implements OnApplicationShutdown {
 		if (name == null) return null;
 		if (host == null) return null;
 
-		const newHost = host === this.config.host ? null : host; 
+		const newHost = host === this.config.host ? null : host;
 
 		const queryOrNull = async () => (await this.emojisRepository.findOneBy({
 			name,
 			host: newHost ?? IsNull(),
 		})) ?? null;
 
-		const emoji = await this.cache.fetch(`${name} ${host}`, queryOrNull);
+		const emoji = await this.emojisCache.fetch(`${name} ${host}`, queryOrNull);
 
 		if (emoji == null) return null;
 		return emoji.publicUrl || emoji.originalUrl; // || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
@@ -384,7 +391,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 	 */
 	@bindThis
 	public async prefetchEmojis(emojis: { name: string; host: string | null; }[]): Promise<void> {
-		const notCachedEmojis = emojis.filter(emoji => this.cache.get(`${emoji.name} ${emoji.host}`) == null);
+		const notCachedEmojis = emojis.filter(emoji => this.emojisCache.get(`${emoji.name} ${emoji.host}`) == null);
 		const emojisQuery: any[] = [];
 		const hosts = new Set(notCachedEmojis.map(e => e.host));
 		for (const host of hosts) {
@@ -399,7 +406,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 			select: ['name', 'host', 'originalUrl', 'publicUrl'],
 		}) : [];
 		for (const emoji of _emojis) {
-			this.cache.set(`${emoji.name} ${emoji.host}`, emoji);
+			this.emojisCache.set(`${emoji.name} ${emoji.host}`, emoji);
 		}
 	}
 
@@ -424,7 +431,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 
 	@bindThis
 	public dispose(): void {
-		this.cache.dispose();
+		this.emojisCache.dispose();
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 22871adb16bc4bc9529f5543b093a65ab54e43c1..4c8a3dc0500fa142044ae177da64df1f68522f29 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -135,6 +135,7 @@ export interface NoteEventTypes {
 	};
 	replied: {
 		id: MiNote['id'];
+		userId: MiUser['id'];
 	};
 }
 type NoteStreamEventTypes = {
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 625df1feaa82fcbb9ddfd3e3541dae3cd32a82e7..76d0eb23398418b0cfce2f244ba2e7e78bba283f 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -6,7 +6,7 @@
 import { URL } from 'node:url';
 import { Inject, Injectable } from '@nestjs/common';
 import * as parse5 from 'parse5';
-import { Window, XMLSerializer } from 'happy-dom';
+import { Window, DocumentFragment, XMLSerializer } from 'happy-dom';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { intersperse } from '@/misc/prelude/array.js';
@@ -483,6 +483,8 @@ export class MfmService {
 
 		const doc = window.document;
 
+		const body = doc.createElement('p');
+
 		async function appendChildren(children: mfm.MfmNode[], targetElement: any): Promise<void> {
 			if (children) {
 				for (const child of await Promise.all(children.map(async (x) => await (handlers as any)[x.type](x)))) targetElement.appendChild(child);
@@ -661,7 +663,7 @@ export class MfmService {
 			},
 		};
 
-		await appendChildren(nodes, doc.body);
+		await appendChildren(nodes, body);
 
 		if (quoteUri !== null) {
 			const a = doc.createElement('a');
@@ -675,9 +677,15 @@ export class MfmService {
 			quote.innerHTML += 'RE: ';
 			quote.appendChild(a);
 
-			doc.body.appendChild(quote);
+			body.appendChild(quote);
+		}
+
+		let result = new XMLSerializer().serializeToString(body);
+
+		if (inline) {
+			result = result.replace(/^<p>/,'').replace(/<\/p>$/,'');
 		}
 
-		return inline ? doc.body.innerHTML : `<p>${doc.body.innerHTML}</p>`;
+		return result;
 	}
 }
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 41efa76f3f6ede0c0c11667f6a6dc0bd8726843b..44b066444dfbd00e05ba7b5e2e60a5ad2360058b 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -831,6 +831,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 			if (data.reply) {
 				this.globalEventService.publishNoteStream(data.reply.id, 'replied', {
 					id: note.id,
+					userId: user.id,
 				});
 				// 通知
 				if (data.reply.userHost === null) {
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index e9dc9b57afef6415d7eda5ccdac9a974c7d40fb0..91857dc683d268d1938cf8c665831ec29bf5c2f1 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -35,7 +35,7 @@ export class RelayService {
 		private createSystemUserService: CreateSystemUserService,
 		private apRendererService: ApRendererService,
 	) {
-		this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10);
+		this.relaysCache = new MemorySingleCache<MiRelay[]>(1000 * 60 * 10); // 10m
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index f5a753afc717b6868277fdc43860d89ed2ced4d2..2b6089fd3a0072db87b005453fa7c31fc407479e 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -129,10 +129,8 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 		private moderationLogService: ModerationLogService,
 		private fanoutTimelineService: FanoutTimelineService,
 	) {
-		//this.onMessage = this.onMessage.bind(this);
-
-		this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60 * 1);
-		this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 60 * 1);
+		this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h
+		this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m
 
 		this.redisForSub.on('message', this.onMessage);
 	}
diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts
index 51ac99179a6a8828309d5b76c2b03dfccfc0c464..92d61cd103f7347156191dd92284666cb26b3f9a 100644
--- a/packages/backend/src/core/UserKeypairService.ts
+++ b/packages/backend/src/core/UserKeypairService.ts
@@ -25,7 +25,7 @@ export class UserKeypairService implements OnApplicationShutdown {
 	) {
 		this.cache = new RedisKVCache<MiUserKeypair>(this.redisClient, 'userKeypair', {
 			lifetime: 1000 * 60 * 60 * 24, // 24h
-			memoryCacheLifetime: Infinity,
+			memoryCacheLifetime: 1000 * 60 * 60, // 1h
 			fetcher: (key) => this.userKeypairsRepository.findOneByOrFail({ userId: key }),
 			toRedisConverter: (value) => JSON.stringify(value),
 			fromRedisConverter: (value) => JSON.parse(value),
diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts
index cbbac52cdb3a4172a13dfb2692eb4c58da4d17a9..1517dd00744509e35306c6546f29342674448259 100644
--- a/packages/backend/src/core/WebfingerService.ts
+++ b/packages/backend/src/core/WebfingerService.ts
@@ -22,7 +22,9 @@ export type IWebFinger = {
 const urlRegex = /^https?:\/\//;
 const mRegex = /^([^@]+)@(.*)/;
 
-const defaultProtocol = process.env.MISSKEY_WEBFINGER_USE_HTTP?.toLowerCase() === 'true' ? 'http' : 'https';
+// we have the colons here, because URL.protocol does as well, so it's
+// more uniform in the places we use both
+const defaultProtocol = process.env.MISSKEY_WEBFINGER_USE_HTTP?.toLowerCase() === 'true' ? 'http:' : 'https:';
 
 @Injectable()
 export class WebfingerService {
@@ -82,7 +84,7 @@ export class WebfingerService {
 		const m = query.match(mRegex);
 		if (m) {
 			const hostname = m[2];
-			return `${defaultProtocol}://${hostname}/.well-known/host-meta`;
+			return `${defaultProtocol}//${hostname}/.well-known/host-meta`;
 		}
 
 		throw new Error(`Invalid query (${query})`);
@@ -101,7 +103,7 @@ export class WebfingerService {
 			const template = (hostMeta['XRD']['Link'] as Array<any>).filter(p => p['@_rel'] === 'lrdd')[0]['@_template'];
 			return template.indexOf('{uri}') < 0 ? null : template;
 		} catch (err) {
-			console.error(`error while request host-meta for ${url}`);
+			console.error(`error while request host-meta for ${url}: ${err}`);
 			return null;
 		}
 	}
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index 44680a2ed5d5d4e009d2032a2ed3ce1d204d0e22..062af397321e55e5eafd389250165627f087991d 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -54,8 +54,8 @@ export class ApDbResolverService implements OnApplicationShutdown {
 		private cacheService: CacheService,
 		private apPersonService: ApPersonService,
 	) {
-		this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(Infinity);
-		this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(Infinity);
+		this.publicKeyCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
+		this.publicKeyByUserIdCache = new MemoryKVCache<MiUserPublickey | null>(1000 * 60 * 60 * 12); // 12h
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 90784fdc1d662478d06222a1d752903a10f81190..98fc647a838fab28cff4bb08a61e5d778717c4df 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -793,6 +793,13 @@ export class ApRendererService {
 
 	@bindThis
 	public async attachLdSignature(activity: any, user: { id: MiUser['id']; host: null; }): Promise<IActivity> {
+		// Linked Data signatures are cryptographic signatures attached to each activity to provide proof of authenticity.
+		// When using authorized fetch, this is often undesired as any signed activity can be forwarded to a blocked instance by relays and other instances.
+		// This setting allows admins to disable LD signatures for increased privacy, at the expense of fewer relayed activities and additional inbound fetch (GET) requests.
+		if (!this.config.attachLdSignatureForRelays) {
+			return activity;
+		}
+
 		const keypair = await this.userKeypairService.getUserKeypair(user.id);
 
 		const jsonLd = this.jsonLdService.use();
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index 3d98c5b76443e71c1c844a508222ce684ae60f8f..b281ac9728fe6e96daff02cb026cc9bc5f021e00 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -82,7 +82,7 @@ export class ApImageService {
 			url: image.url,
 			user: actor,
 			uri: image.url,
-			sensitive: image.sensitive,
+			sensitive: !!(image.sensitive),
 			isLink: !shouldBeCached,
 			comment: truncate(image.name ?? undefined, DB_MAX_IMAGE_COMMENT_LENGTH),
 		});
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index 002a93397de7bf26258e059285a90628a37e0839..2dfb8d5356877b23f5e6304d481d07bb63972ef0 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -63,8 +63,9 @@ export class InstanceEntityService {
 	@bindThis
 	public packMany(
 		instances: MiInstance[],
+		me?: { id: MiUser['id']; } | null | undefined,
 	) {
-		return Promise.all(instances.map(x => this.pack(x)));
+		return Promise.all(instances.map(x => this.pack(x, me)));
 	}
 }
 
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index bba64a06eff2dbb2dd28faa5bad1bb7041e08022..d968069ca35563d770179f0a12a577a213c27471 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -7,23 +7,23 @@ import * as Redis from 'ioredis';
 import { bindThis } from '@/decorators.js';
 
 export class RedisKVCache<T> {
-	private redisClient: Redis.Redis;
-	private name: string;
-	private lifetime: number;
-	private memoryCache: MemoryKVCache<T>;
-	private fetcher: (key: string) => Promise<T>;
-	private toRedisConverter: (value: T) => string;
-	private fromRedisConverter: (value: string) => T | undefined;
-
-	constructor(redisClient: RedisKVCache<T>['redisClient'], name: RedisKVCache<T>['name'], opts: {
-		lifetime: RedisKVCache<T>['lifetime'];
-		memoryCacheLifetime: number;
-		fetcher: RedisKVCache<T>['fetcher'];
-		toRedisConverter: RedisKVCache<T>['toRedisConverter'];
-		fromRedisConverter: RedisKVCache<T>['fromRedisConverter'];
-	}) {
-		this.redisClient = redisClient;
-		this.name = name;
+	private readonly lifetime: number;
+	private readonly memoryCache: MemoryKVCache<T>;
+	private readonly fetcher: (key: string) => Promise<T>;
+	private readonly toRedisConverter: (value: T) => string;
+	private readonly fromRedisConverter: (value: string) => T | undefined;
+
+	constructor(
+		private redisClient: Redis.Redis,
+		private name: string,
+		opts: {
+			lifetime: RedisKVCache<T>['lifetime'];
+			memoryCacheLifetime: number;
+			fetcher: RedisKVCache<T>['fetcher'];
+			toRedisConverter: RedisKVCache<T>['toRedisConverter'];
+			fromRedisConverter: RedisKVCache<T>['fromRedisConverter'];
+		},
+	) {
 		this.lifetime = opts.lifetime;
 		this.memoryCache = new MemoryKVCache(opts.memoryCacheLifetime);
 		this.fetcher = opts.fetcher;
@@ -55,7 +55,13 @@ export class RedisKVCache<T> {
 
 		const cached = await this.redisClient.get(`kvcache:${this.name}:${key}`);
 		if (cached == null) return undefined;
-		return this.fromRedisConverter(cached);
+
+		const value = this.fromRedisConverter(cached);
+		if (value !== undefined) {
+			this.memoryCache.set(key, value);
+		}
+
+		return value;
 	}
 
 	@bindThis
@@ -77,14 +83,14 @@ export class RedisKVCache<T> {
 
 		// Cache MISS
 		const value = await this.fetcher(key);
-		this.set(key, value);
+		await this.set(key, value);
 		return value;
 	}
 
 	@bindThis
 	public async refresh(key: string) {
 		const value = await this.fetcher(key);
-		this.set(key, value);
+		await this.set(key, value);
 
 		// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
 	}
@@ -101,23 +107,23 @@ export class RedisKVCache<T> {
 }
 
 export class RedisSingleCache<T> {
-	private redisClient: Redis.Redis;
-	private name: string;
-	private lifetime: number;
-	private memoryCache: MemorySingleCache<T>;
-	private fetcher: () => Promise<T>;
-	private toRedisConverter: (value: T) => string;
-	private fromRedisConverter: (value: string) => T | undefined;
-
-	constructor(redisClient: RedisSingleCache<T>['redisClient'], name: RedisSingleCache<T>['name'], opts: {
-		lifetime: RedisSingleCache<T>['lifetime'];
-		memoryCacheLifetime: number;
-		fetcher: RedisSingleCache<T>['fetcher'];
-		toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
-		fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
-	}) {
-		this.redisClient = redisClient;
-		this.name = name;
+	private readonly lifetime: number;
+	private readonly memoryCache: MemorySingleCache<T>;
+	private readonly fetcher: () => Promise<T>;
+	private readonly toRedisConverter: (value: T) => string;
+	private readonly fromRedisConverter: (value: string) => T | undefined;
+
+	constructor(
+		private redisClient: Redis.Redis,
+		private name: string,
+		opts: {
+			lifetime: number;
+			memoryCacheLifetime: number;
+			fetcher: RedisSingleCache<T>['fetcher'];
+			toRedisConverter: RedisSingleCache<T>['toRedisConverter'];
+			fromRedisConverter: RedisSingleCache<T>['fromRedisConverter'];
+		},
+	) {
 		this.lifetime = opts.lifetime;
 		this.memoryCache = new MemorySingleCache(opts.memoryCacheLifetime);
 		this.fetcher = opts.fetcher;
@@ -149,7 +155,13 @@ export class RedisSingleCache<T> {
 
 		const cached = await this.redisClient.get(`singlecache:${this.name}`);
 		if (cached == null) return undefined;
-		return this.fromRedisConverter(cached);
+
+		const value = this.fromRedisConverter(cached);
+		if (value !== undefined) {
+			this.memoryCache.set(value);
+		}
+
+		return value;
 	}
 
 	@bindThis
@@ -171,14 +183,14 @@ export class RedisSingleCache<T> {
 
 		// Cache MISS
 		const value = await this.fetcher();
-		this.set(value);
+		await this.set(value);
 		return value;
 	}
 
 	@bindThis
 	public async refresh() {
 		const value = await this.fetcher();
-		this.set(value);
+		await this.set(value);
 
 		// TODO: イベント発行して他プロセスのメモリキャッシュも更新できるようにする
 	}
@@ -187,22 +199,12 @@ export class RedisSingleCache<T> {
 // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
 
 export class MemoryKVCache<T> {
-	/**
-	 * データを持つマップ
-	 * @deprecated これを直接操作するべきではない
-	 */
-	public cache: Map<string, { date: number; value: T; }>;
-	private lifetime: number;
-	private gcIntervalHandle: NodeJS.Timeout;
+	private readonly cache = new Map<string, { date: number; value: T; }>();
+	private readonly gcIntervalHandle = setInterval(() => this.gc(), 1000 * 60 * 3); // 3m
 
-	constructor(lifetime: MemoryKVCache<never>['lifetime']) {
-		this.cache = new Map();
-		this.lifetime = lifetime;
-
-		this.gcIntervalHandle = setInterval(() => {
-			this.gc();
-		}, 1000 * 60 * 3);
-	}
+	constructor(
+		private readonly lifetime: number,
+	) {}
 
 	@bindThis
 	/**
@@ -287,10 +289,14 @@ export class MemoryKVCache<T> {
 	@bindThis
 	public gc(): void {
 		const now = Date.now();
+
 		for (const [key, { date }] of this.cache.entries()) {
-			if ((now - date) > this.lifetime) {
-				this.cache.delete(key);
-			}
+			// The map is ordered from oldest to youngest.
+			// We can stop once we find an entry that's still active, because all following entries must *also* be active.
+			const age = now - date;
+			if (age < this.lifetime) break;
+
+			this.cache.delete(key);
 		}
 	}
 
@@ -298,16 +304,19 @@ export class MemoryKVCache<T> {
 	public dispose(): void {
 		clearInterval(this.gcIntervalHandle);
 	}
+
+	public get entries() {
+		return this.cache.entries();
+	}
 }
 
 export class MemorySingleCache<T> {
 	private cachedAt: number | null = null;
 	private value: T | undefined;
-	private lifetime: number;
 
-	constructor(lifetime: MemorySingleCache<never>['lifetime']) {
-		this.lifetime = lifetime;
-	}
+	constructor(
+		private lifetime: number,
+	) {}
 
 	@bindThis
 	public set(value: T): void {
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 4a1b42383f19ef5ce29b2c4a16d507126483c167..b7c418fa5936217faa5dd483f466afb0bc4dc54d 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -236,12 +236,8 @@ export function createPostgresDataSource(config: Config) {
 		cache: !config.db.disableCache && process.env.NODE_ENV !== 'test' ? { // dbをcloseしても何故かredisのコネクションが内部的に残り続けるようで、テストの際に支障が出るため無効にする(キャッシュも含めてテストしたいため本当は有効にしたいが...)
 			type: 'ioredis',
 			options: {
-				host: config.redis.host,
-				port: config.redis.port,
-				family: config.redis.family ?? 0,
-				password: config.redis.pass,
+				...config.redis,
 				keyPrefix: `${config.redis.prefix}:query:`,
-				db: config.redis.db ?? 0,
 			},
 		} : false,
 		logging: log,
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index d665945861e2172f4c0174ffe440b146fcde87cb..4076e9da9038f8ca7f5d5c66b882745bc6127dfd 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -45,7 +45,7 @@ export class DeliverProcessorService {
 		private queueLoggerService: QueueLoggerService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('deliver');
-		this.suspendedHostsCache = new MemorySingleCache<MiInstance[]>(1000 * 60 * 60);
+		this.suspendedHostsCache = new MemorySingleCache<MiInstance[]>(1000 * 60 * 60); // 1h
 	}
 
 	@bindThis
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 716bb0944b635b13017c80876733a13e1c219282..bc8d3c0411d00418958ebb96c776daa286db6706 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -135,7 +135,7 @@ export class NodeinfoServerService {
 			return document;
 		};
 
-		const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
+		const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10); // 10m
 
 		fastify.get(nodeinfo2_1path, async (request, reply) => {
 			const base = await cache.fetch(() => nodeinfo2(21));
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index ddef8db9879ac82b5ebba604ac4b00cbe8428c5f..690ff2e022822659f0bb691ee3a75655e55143a0 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -37,7 +37,7 @@ export class AuthenticateService implements OnApplicationShutdown {
 
 		private cacheService: CacheService,
 	) {
-		this.appCache = new MemoryKVCache<MiApp>(Infinity);
+		this.appCache = new MemoryKVCache<MiApp>(1000 * 60 * 60 * 24 * 7); // 1w
 	}
 
 	@bindThis
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index c3f2247b69591c71aa264d40ac26d27a01c52ea3..c1ce3f2238c5d9c8fe32798c79381a35ab88f6ae 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -203,7 +203,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const instances = await query.limit(ps.limit).offset(ps.offset).getMany();
 
-			return await this.instanceEntityService.packMany(instances);
+			return await this.instanceEntityService.packMany(instances, me);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts
index bac54970ab853c69993670c9e5932870c7304b94..69900bff9acae94cb7cca44800b923dfa4b1eba5 100644
--- a/packages/backend/src/server/api/endpoints/federation/stats.ts
+++ b/packages/backend/src/server/api/endpoints/federation/stats.ts
@@ -107,9 +107,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const gotPubCount = topPubInstances.map(x => x.followingCount).reduce((a, b) => a + b, 0);
 
 			return await awaitAll({
-				topSubInstances: this.instanceEntityService.packMany(topSubInstances),
+				topSubInstances: this.instanceEntityService.packMany(topSubInstances, me),
 				otherFollowersCount: Math.max(0, allSubCount - gotSubCount),
-				topPubInstances: this.instanceEntityService.packMany(topPubInstances),
+				topPubInstances: this.instanceEntityService.packMany(topPubInstances, me),
 				otherFollowingCount: Math.max(0, allPubCount - gotPubCount),
 			});
 		});
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 a4e61a0e8f2fab268414ad221c475b5789ceb922..084d4af658178e57e0ad24c85e25d834f3e3fa3a 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
@@ -14,12 +14,19 @@ import type { UserProfilesRepository, UserSecurityKeysRepository } from '@/model
 import { WebAuthnService } from '@/core/WebAuthnService.js';
 import { ApiError } from '@/server/api/error.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
 	secure: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	errors: {
 		incorrectPassword: {
 			message: 'Incorrect password.',
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 cc6e9ee42d242ffb144c0e4478731c38ecc10d43..6ab50a57c93fbcd0f8633fd0759f89fb3c0d9039 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
@@ -12,12 +12,19 @@ import { DI } from '@/di-symbols.js';
 import { WebAuthnService } from '@/core/WebAuthnService.js';
 import { ApiError } from '@/server/api/error.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
 	secure: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	errors: {
 		userNotFound: {
 			message: 'User not found.',
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 7283159f874e55f4c4870a25c4f7736831cba42c..888d0fc6efe2ca341710718f03a362dfae11d705 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -14,12 +14,19 @@ import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { ApiError } from '@/server/api/error.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
 	secure: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	errors: {
 		incorrectPassword: {
 			message: 'Incorrect password.',
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 098fd5930328218462c7c8649afd2b3eb72ebb55..614fd0c498b86073360b1f53ed613c646e314289 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
@@ -13,10 +13,17 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '@/server/api/error.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	secure: true,
 
 	errors: {
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 8da331505b82428ea8daa9568826e48bc0080d9a..27738253734fb4b67678a290401a3c2219dd3af8 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -13,12 +13,19 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
 import { ApiError } from '@/server/api/error.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
 	secure: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	errors: {
 		incorrectPassword: {
 			message: 'Incorrect password.',
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 6aedde717c93f65000c2b4ee53690290bd63e479..f131c7e9d12f7c0b4c46b501bea72376a6c06259 100644
--- a/packages/backend/src/server/api/endpoints/i/change-password.ts
+++ b/packages/backend/src/server/api/endpoints/i/change-password.ts
@@ -10,10 +10,17 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { UserProfilesRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	secure: true,
 } as const;
 
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 af4d601ad6a86d9e298234a41f47a2c4d8d2d762..565eaaafc01e3d870d498f6e601e09bb5f6fa0b4 100644
--- a/packages/backend/src/server/api/endpoints/i/delete-account.ts
+++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts
@@ -11,10 +11,17 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DeleteAccountService } from '@/core/DeleteAccountService.js';
 import { DI } from '@/di-symbols.js';
 import { UserAuthService } from '@/core/UserAuthService.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	secure: true,
 } as const;
 
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 e1cdfdc18548b8abdfe701dfa3a0f76182bc8406..814ffb5488bd98041a0b552e3c8207668bef4227 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -11,10 +11,17 @@ import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
 import generateUserToken from '@/misc/generate-native-user-token.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { DI } from '@/di-symbols.js';
+import ms from 'ms';
 
 export const meta = {
 	requireCredential: true,
 
+	limit: {
+		duration: ms('1hour'),
+		max: 10,
+		minInterval: ms('1sec'),
+	},
+
 	secure: true,
 } as const;
 
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index 41c0feccc7e37df269bb1657f06cd71470bc9c34..7dd7db24e5b417a2bfdc64858a89e78b026e8c53 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -201,6 +201,18 @@ export default class Connection {
 
 	@bindThis
 	private async onNoteStreamMessage(data: GlobalEvents['note']['payload']) {
+		// we must not send to the frontend information about notes from
+		// users who blocked the logged-in user, even when they're replies
+		// to notes the logged-in user can see
+		if (data.type === 'replied') {
+			const noteUserId = data.body.body.userId;
+			if (noteUserId !== null) {
+				if (this.userIdsWhoBlockingMe.has(noteUserId)) {
+					return;
+				}
+			}
+		}
+
 		this.sendMessageToWs('noteUpdated', {
 			id: data.body.id,
 			type: data.type,
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index 96038d9c1ea26d798cc49cfdf52939d32a4ca594..ef804b5bfdbc613814ae3e0e2b325b4c583cf6c9 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -38,8 +38,8 @@ export class UrlPreviewService {
 	) {
 		this.logger = this.loggerService.getLogger('url-preview');
 		this.previewCache = new RedisKVCache<SummalyResult>(this.redisClient, 'summaly', {
-			lifetime: 1000 * 86400,
-			memoryCacheLifetime: 1000 * 10 * 60,
+			lifetime: 1000 * 60 * 60 * 24, // 1d
+			memoryCacheLifetime: 1000 * 60 * 10, // 10m
 			fetcher: (key: string) => { throw new Error('the UrlPreview cache should never fetch'); },
 			toRedisConverter: (value) => JSON.stringify(value),
 			fromRedisConverter: (value) => JSON.parse(value),
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 3bf44aea8ed8fbb04dc068b8140b56897f3ceaa2..112a84f1fd3a0a47bbfa786961c7433849d4a865 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -37,7 +37,7 @@ import 'photoswipe/style.css';
 import XBanner from '@/components/MkMediaBanner.vue';
 import XImage from '@/components/MkMediaImage.vue';
 import XVideo from '@/components/MkMediaVideo.vue';
-import XModPlayer from '@/components/MkModPlayer.vue';
+import XModPlayer from '@/components/SkModPlayer.vue';
 import * as os from '@/os.js';
 import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES } from '@/const.js';
 import { defaultStore } from '@/store.js';
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index b8ce7ed8303af170e9685a11dfece2fe57170e82..b6ded3167b74de597bb61f470fa5c4d5ff9a913d 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 >
 	<div v-if="appearNote.reply && inReplyToCollapsed" :class="$style.collapsedInReplyTo">
 		<MkAvatar :class="$style.collapsedInReplyToAvatar" :user="appearNote.reply.user" link preview/>
-		<MkA v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
+		<MkA v-user-preview="appearNote.reply.userId" :class="$style.name" :to="userPage(appearNote.reply.user)">
 			<MkAcct :user="appearNote.reply.user"/>
 		</MkA>:
 		<Mfm :text="getNoteSummary(appearNote.reply)" :plain="true" :nowrap="true" :author="appearNote.reply.user" :nyaize="'respect'" :class="$style.collapsedInReplyToText" @click="inReplyToCollapsed = false"/>
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index 5240d646612605360585ab23f3f996b61e020867..15173fbd99704b84602c0631e0d6e3201d5436b2 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -15,7 +15,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #default="{ items: notes }">
 		<div :class="[$style.root, { [$style.noGap]: noGap }]">
 			<MkDateSeparatedList
-				v-if="defaultStore.state.noteDesign === 'misskey'"
 				ref="notes"
 				v-slot="{ item: note }"
 				:items="notes"
@@ -27,34 +26,25 @@ SPDX-License-Identifier: AGPL-3.0-only
 			>
 				<MkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
 			</MkDateSeparatedList>
-			<MkDateSeparatedList
-				v-else-if="defaultStore.state.noteDesign === 'sharkey'"
-				ref="notes" 
-				v-slot="{ item: note }"
-				:items="notes"
-				:direction="pagination.reversed ? 'up' : 'down'"
-				:reversed="pagination.reversed"
-				:noGap="noGap"
-				:ad="true"
-				:class="$style.notes"
-			>
-				<SkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
-			</MkDateSeparatedList>
 		</div>
 	</template>
 </MkPagination>
 </template>
 
 <script lang="ts" setup>
-import { shallowRef, ref } from 'vue';
-import MkNote from '@/components/MkNote.vue';
-import SkNote from '@/components/SkNote.vue';
+import { defineAsyncComponent, shallowRef, ref } from 'vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import { i18n } from '@/i18n.js';
 import { infoImageUrl } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 
+const MkNote = defineAsyncComponent(() =>
+	(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
+	(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
+	null
+);
+
 const props = defineProps<{
 	pagination: Paging;
 	noGap?: boolean;
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 68bf1bf3d8f252e43de75e4ec6aaa579d4d07c65..2dd6c21ef6d383256cbb3ff78e6142234d6b0106 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -14,26 +14,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</template>
 
 		<template #default="{ items: notifications }">
-			<MkDateSeparatedList v-if="defaultStore.state.noteDesign === 'misskey'" v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
+			<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
 				<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/>
 				<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
 			</MkDateSeparatedList>
-			<MkDateSeparatedList v-else-if="defaultStore.state.noteDesign === 'sharkey'" v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
-				<SkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/>
-				<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
-			</MkDateSeparatedList>
 		</template>
 	</MkPagination>
 </MkPullToRefresh>
 </template>
 
 <script lang="ts" setup>
-import { onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
+import { defineAsyncComponent, onUnmounted, onDeactivated, onMounted, computed, shallowRef, onActivated } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import XNotification from '@/components/MkNotification.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
-import MkNote from '@/components/MkNote.vue';
-import SkNote from '@/components/SkNote.vue';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import { notificationTypes } from '@/const.js';
@@ -42,6 +36,12 @@ import { defaultStore } from '@/store.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 import * as Misskey from 'misskey-js';
 
+const MkNote = defineAsyncComponent(() =>
+	(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
+	(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
+	null
+);
+
 const props = defineProps<{
 	excludeTypes?: typeof notificationTypes[number][];
 }>();
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index 57ed0458aad743ab25eaceea4152d8dffe0b50ea..78df70ca5cdc72348c796de4609cbfdaf47eed44 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -372,7 +372,7 @@ function watchForDraft() {
 }
 
 function MFMWindow() {
-	os.popup(defineAsyncComponent(() => import('@/components/MkMfmWindow.vue')), {}, {}, 'closed');
+	os.popup(defineAsyncComponent(() => import('@/components/SkMfmWindow.vue')), {}, {}, 'closed');
 }
 
 function checkMissingMention() {
@@ -777,7 +777,9 @@ async function post(ev?: MouseEvent) {
 	if (defaultStore.state.warnMissingAltText) {
 		const filesData = toRaw(files.value);
 
-		const isMissingAltText = filesData.some(file => !file.comment);
+		const isMissingAltText = filesData.filter(
+			file => file.type.startsWith('image/') || file.type.startsWith('video/') || file.type.startsWith('audio/')
+		).some(file => !file.comment);
 
 		if (isMissingAltText) {
 			const { canceled, result } = await os.actions({
diff --git a/packages/frontend/src/components/MkFormula.vue b/packages/frontend/src/components/SkFormula.vue
similarity index 100%
rename from packages/frontend/src/components/MkFormula.vue
rename to packages/frontend/src/components/SkFormula.vue
diff --git a/packages/frontend/src/components/MkMfmWindow.vue b/packages/frontend/src/components/SkMfmWindow.vue
similarity index 100%
rename from packages/frontend/src/components/MkMfmWindow.vue
rename to packages/frontend/src/components/SkMfmWindow.vue
diff --git a/packages/frontend/src/components/MkModPlayer.vue b/packages/frontend/src/components/SkModPlayer.vue
similarity index 100%
rename from packages/frontend/src/components/MkModPlayer.vue
rename to packages/frontend/src/components/SkModPlayer.vue
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index e8d51c850a3b5e1ba9e0df1dfb159a2513a7a565..98f250c8b35d16cf846e49956d7554ad37ee650f 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div v-if="appearNote.reply && inReplyToCollapsed && !renoteCollapsed" :class="$style.collapsedInReplyTo">
 		<div :class="$style.collapsedInReplyToLine"></div>
 		<MkAvatar :class="$style.collapsedInReplyToAvatar" :user="appearNote.reply.user" link preview/>
-		<MkA v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
+		<MkA v-user-preview="appearNote.reply.userId" :class="$style.name" :to="userPage(appearNote.reply.user)">
 			<MkAcct :user="appearNote.reply.user"/>
 		</MkA>:
 		<Mfm :text="getNoteSummary(appearNote.reply)" :plain="true" :nowrap="true" :author="appearNote.reply.user" :nyaize="'respect'" :class="$style.collapsedInReplyToText" @click="inReplyToCollapsed = false"/>
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
index 762f921a7c806ebd6ea7006a0f501b7df72df9f6..ac563ffaf5700f9486923a15bf8e91b3591a35c1 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -80,7 +80,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 
 	const isBlock = props.isBlock ?? false;
 
-	const MkFormula = defineAsyncComponent(() => import('@/components/MkFormula.vue'));
+	const SkFormula = defineAsyncComponent(() => import('@/components/SkFormula.vue'));
 
 	/**
 	 * Gen Vue Elements from MFM AST
@@ -499,14 +499,14 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven
 			}
 
 			case 'mathInline': {
-				return [h('bdi', h(MkFormula, {
+				return [h('bdi', h(SkFormula, {
 					formula: token.props.formula,
 					block: false,
 				}))];
 			}
 
 			case 'mathBlock': {
-				return [h('bdi', { class: 'block' }, h(MkFormula, {
+				return [h('bdi', { class: 'block' }, h(SkFormula, {
 					formula: token.props.formula,
 					block: true,
 				}))];
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index dc28d93485e05aeb657f304a1b66001f66ca25a3..cb1feef0166462bb83dc0772fb4c5bd3e4569975 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -16,14 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</template>
 
 			<template #default="{ items }">
-				<MkDateSeparatedList v-if="defaultStore.state.noteDesign === 'misskey'"
-					v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
+				<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
 					<MkNote :key="item.id" :note="item.note" :class="$style.note"/>
 				</MkDateSeparatedList>
-				<MkDateSeparatedList v-if="defaultStore.state.noteDesign === 'sharkey'"
-					v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
-					<SkNote :key="item.id" :note="item.note" :class="$style.note"/>
-				</MkDateSeparatedList>
 			</template>
 		</MkPagination>
 	</MkSpacer>
@@ -32,14 +27,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import MkPagination from '@/components/MkPagination.vue';
-import MkNote from '@/components/MkNote.vue';
-import SkNote from '@/components/SkNote.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
+import { defineAsyncComponent } from 'vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { infoImageUrl } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 
+const MkNote = defineAsyncComponent(() =>
+	(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
+	(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
+	null
+);
+
 const pagination = {
 	endpoint: 'i/favorites' as const,
 	limit: 10,
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index 9b5289786fae325eb64966036727dfe3eb563794..d0bbaeddd96bd4fe961301eaf6bd4a14b24c0d08 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -19,14 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ti ti-chevron-up"></i> <i class="ti ti-device-tv"></i></MkButton>
 							<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ti ti-chevron-up"></i> <i class="ti ti-user"></i></MkButton>
 						</div>
-						<div v-if="defaultStore.state.noteDesign === 'misskey'" class="_margin _gaps_s">
+						<div class="_margin _gaps_s">
 							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
 							<MkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note" :expandAllCws="expandAllCws"/>
 						</div>
-						<div v-else-if="defaultStore.state.noteDesign === 'sharkey'" class="_margin _gaps_s">
-							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
-							<SkNoteDetailed :key="note.id" v-model:note="note" :initialTab="initialTab" :class="$style.note" :expandAllCws="expandAllCws"/>
-						</div>
 						<div v-if="clips && clips.length > 0" class="_margin">
 							<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
 							<div class="_gaps">
@@ -52,12 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, ref } from 'vue';
+import { defineAsyncComponent, computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import type { Paging } from '@/components/MkPagination.vue';
-import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import MkNotes from '@/components/MkNotes.vue';
-import SkNoteDetailed from '@/components/SkNoteDetailed.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkButton from '@/components/MkButton.vue';
 import { misskeyApi } from '@/scripts/misskey-api.js';
@@ -67,6 +61,12 @@ import { dateString } from '@/filters/date.js';
 import MkClipPreview from '@/components/MkClipPreview.vue';
 import { defaultStore } from '@/store.js';
 
+const MkNoteDetailed = defineAsyncComponent(() =>
+	(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNoteDetailed.vue') :
+	(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNoteDetailed.vue') :
+	null
+);
+
 const props = defineProps<{
 	noteId: string;
 	initialTab?: string;
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 7ac23b0aaa2efda8aa10e4077015e0ab23876ede..112394170ff42ff689d59c920b621e1c37c3fdd0 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -120,12 +120,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 
 			<div class="contents _gaps">
-				<div v-if="user.pinnedNotes.length > 0 && defaultStore.state.noteDesign === 'misskey'" class="_gaps">
+				<div v-if="user.pinnedNotes.length > 0" class="_gaps">
 					<MkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
 				</div>
-				<div v-else-if="user.pinnedNotes.length > 0 && defaultStore.state.noteDesign === 'sharkey'" class="_gaps">
-					<SkNote v-for="note in user.pinnedNotes" :key="note.id" class="note _panel" :note="note" :pinned="true"/>
-				</div>
 				<MkInfo v-else-if="$i && $i.id === user.id">{{ i18n.ts.userPagePinTip }}</MkInfo>
 				<template v-if="narrow">
 					<MkLazy>
@@ -171,9 +168,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkTab from '@/components/MkTab.vue';
-import MkNote from '@/components/MkNote.vue';
 import MkNotes from '@/components/MkNotes.vue';
-import SkNote from '@/components/SkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import MkAccountMoved from '@/components/MkAccountMoved.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
@@ -195,6 +190,12 @@ import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFf
 import { useRouter } from '@/router/supplier.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
 
+const MkNote = defineAsyncComponent(() =>
+	(defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNote.vue') :
+	(defaultStore.state.noteDesign === 'sharkey') ? import('@/components/SkNote.vue') :
+	null
+);
+
 function calcAge(birthdate: string): number {
 	const date = new Date(birthdate);
 	const now = new Date();
diff --git a/packages/frontend/vite.replaceIcons.ts b/packages/frontend/vite.replaceIcons.ts
index 3212c9a38a7f67428d4fa5d53352b98724b2d181..494a327477d0de11ac49f437d86d14c916644362 100644
--- a/packages/frontend/vite.replaceIcons.ts
+++ b/packages/frontend/vite.replaceIcons.ts
@@ -5,10 +5,9 @@ function iconsReplace(opts: RollupReplaceOptions) {
 	return pluginReplace({
 		...opts,
 		preventAssignment: false,
-		// only replace these strings at the start of strings, remove a
-		// `ti-fw` it if happens to be just after, and make sure they're
-		// followed by a word-boundary that's not a dash
-		delimiters: ['(?<=["\'`])', '(?: ti-fw)?\\b(?!-)'],
+		// only replace these strings at the start of strings, and make
+		// sure they're followed by a word-boundary that's not a dash
+		delimiters: ['(?<=["\'`])', '\\b(?!-)'],
 	});
 }