From 41592eafb363e3c62ab2d3e5f41b38d7d083d3fb Mon Sep 17 00:00:00 2001
From: syuilo <4439005+syuilo@users.noreply.github.com>
Date: Fri, 9 Jan 2026 22:06:40 +0900
Subject: refactor: make noImplicitAny true (#17083)

* wip

* Update emojis.emoji.vue

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update manager.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update analytics.ts
---
 packages/frontend/src/components/MkExtensionInstaller.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue
index c9d18ee731..9c0cce7689 100644
--- a/packages/frontend/src/components/MkExtensionInstaller.vue
+++ b/packages/frontend/src/components/MkExtensionInstaller.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #key>{{ i18n.ts.permission }}</template>
 					<template #value>
 						<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
-							<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li>
+							<li v-for="permission in extension.meta.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li>
 						</ul>
 						<template v-else>{{ i18n.ts.none }}</template>
 					</template>
-- 
cgit v1.2.3-freya


From bd81a6c8adb45067bee9582f84855a60a962e92b Mon Sep 17 00:00:00 2001
From: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>
Date: Wed, 14 Jan 2026 14:45:45 +0900
Subject: refactor(frontend): anyを除去2 (#17092)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* wip

* fix types

* fix
---
 packages/backend/src/misc/json-schema.ts           |  2 +
 packages/backend/src/models/Meta.ts                |  6 ++-
 packages/backend/src/models/json-schema/meta.ts    | 23 ++++++++++-
 .../backend/src/server/api/endpoints/admin/meta.ts |  3 +-
 .../src/server/api/endpoints/admin/update-meta.ts  | 20 ++++++++--
 .../frontend/src/components/MkAchievements.vue     |  6 +--
 .../frontend/src/components/MkAutocomplete.vue     | 17 ++++----
 .../src/components/MkExtensionInstaller.vue        |  9 +++--
 .../frontend/src/components/MkNotification.vue     |  2 +-
 .../components/MkPushNotificationAllowButton.vue   |  2 +-
 .../src/components/MkRetentionLineChart.vue        | 17 +++++---
 .../src/components/MkServerSetupWizard.vue         |  2 +-
 .../src/components/MkWidgetSettingsDialog.vue      |  5 ++-
 .../src/components/global/MkCondensedLine.vue      |  2 +-
 packages/frontend/src/instance.ts                  |  6 ---
 packages/frontend/src/pages/admin/branding.vue     | 12 +++---
 packages/frontend/src/pages/auth.form.vue          |  8 +++-
 packages/frontend/src/pages/flash/flash.vue        |  2 +-
 packages/frontend/src/pages/settings/plugin.vue    |  2 +-
 .../frontend/src/pages/settings/preferences.vue    |  4 +-
 packages/frontend/src/plugin.ts                    |  6 +--
 packages/frontend/src/pref-migrate.ts              |  9 +++--
 packages/frontend/src/store.ts                     |  6 +--
 packages/frontend/src/utility/autocomplete.ts      | 45 +++++++++++++---------
 packages/frontend/src/widgets/WidgetAichan.vue     |  2 +-
 packages/frontend/src/widgets/WidgetTimeline.vue   | 10 ++---
 packages/frontend/src/widgets/WidgetTrends.vue     |  2 +-
 packages/frontend/src/widgets/index.ts             |  2 +
 packages/frontend/src/widgets/widget.ts            |  3 +-
 packages/misskey-js/etc/misskey-js.api.md          |  4 ++
 packages/misskey-js/src/autogen/models.ts          |  1 +
 packages/misskey-js/src/autogen/types.ts           | 17 ++++++--
 32 files changed, 164 insertions(+), 93 deletions(-)

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

diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 3fa49e3cd1..cf233defd9 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -64,6 +64,7 @@ import {
 	packedMetaDetailedOnlySchema,
 	packedMetaDetailedSchema,
 	packedMetaLiteSchema,
+	packedMetaClientOptionsSchema,
 } from '@/models/json-schema/meta.js';
 import { packedUserWebhookSchema } from '@/models/json-schema/user-webhook.js';
 import { packedSystemWebhookSchema } from '@/models/json-schema/system-webhook.js';
@@ -135,6 +136,7 @@ export const refs = {
 	MetaLite: packedMetaLiteSchema,
 	MetaDetailedOnly: packedMetaDetailedOnlySchema,
 	MetaDetailed: packedMetaDetailedSchema,
+	MetaClientOptions: packedMetaClientOptionsSchema,
 	UserWebhook: packedUserWebhookSchema,
 	SystemWebhook: packedSystemWebhookSchema,
 	AbuseReportNotificationRecipient: packedAbuseReportNotificationRecipientSchema,
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index a6f68194c5..620853450c 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -725,7 +725,11 @@ export class MiMeta {
 	@Column('jsonb', {
 		default: { },
 	})
-	public clientOptions: Record<string, any>;
+	public clientOptions: {
+		entrancePageStyle: 'classic' | 'simple';
+		showTimelineForVisitor: boolean;
+		showActivitiesForVisitor: boolean;
+	};
 }
 
 export type SoftwareSuspension = {
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
index a0e7d490b3..0c3ec141bc 100644
--- a/packages/backend/src/models/json-schema/meta.ts
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -72,8 +72,7 @@ export const packedMetaLiteSchema = {
 			optional: false, nullable: true,
 		},
 		clientOptions: {
-			type: 'object',
-			optional: false, nullable: false,
+			ref: 'MetaClientOptions',
 		},
 		disableRegistration: {
 			type: 'boolean',
@@ -397,3 +396,23 @@ export const packedMetaDetailedSchema = {
 		},
 	],
 } as const;
+
+export const packedMetaClientOptionsSchema = {
+	type: 'object',
+	optional: false, nullable: false,
+	properties: {
+		entrancePageStyle: {
+			type: 'string',
+			enum: ['classic', 'simple'],
+			optional: false, nullable: false,
+		},
+		showTimelineForVisitor: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		showActivitiesForVisitor: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 2c7f793584..5beed3a7e8 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -428,8 +428,7 @@ export const meta = {
 				optional: false, nullable: true,
 			},
 			clientOptions: {
-				type: 'object',
-				optional: false, nullable: false,
+				ref: 'MetaClientOptions',
 			},
 			description: {
 				type: 'string',
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 b3c2cecc67..7a8dfc4555 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -3,7 +3,8 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { Injectable, Inject } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
 import type { MiMeta } from '@/models/Meta.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -67,7 +68,14 @@ export const paramDef = {
 		description: { type: 'string', nullable: true },
 		defaultLightTheme: { type: 'string', nullable: true },
 		defaultDarkTheme: { type: 'string', nullable: true },
-		clientOptions: { type: 'object', nullable: false },
+		clientOptions: {
+			type: 'object', nullable: false,
+			properties: {
+				entrancePageStyle: { type: 'string', nullable: false, enum: ['classic', 'simple'] },
+				showTimelineForVisitor: { type: 'boolean', nullable: false },
+				showActivitiesForVisitor: { type: 'boolean', nullable: false },
+			},
+		},
 		cacheRemoteFiles: { type: 'boolean' },
 		cacheRemoteSensitiveFiles: { type: 'boolean' },
 		emailRequiredForSignup: { type: 'boolean' },
@@ -217,6 +225,9 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.meta)
+		private serverSettings: MiMeta,
+
 		private metaService: MetaService,
 		private moderationLogService: ModerationLogService,
 	) {
@@ -329,7 +340,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			if (ps.clientOptions !== undefined) {
-				set.clientOptions = ps.clientOptions;
+				set.clientOptions = {
+					...serverSettings.clientOptions,
+					...ps.clientOptions,
+				};
 			}
 
 			if (ps.cacheRemoteFiles !== undefined) {
diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue
index d0e138c229..fe6415eabb 100644
--- a/packages/frontend/src/components/MkAchievements.vue
+++ b/packages/frontend/src/components/MkAchievements.vue
@@ -23,13 +23,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<div :class="$style.body">
 				<div :class="$style.header">
-					<span :class="$style.title">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].title }}</span>
+					<span :class="$style.title">{{ i18n.ts._achievements._types[`_${achievement.name}`].title }}</span>
 					<span :class="$style.time">
 						<time v-tooltip="new Date(achievement.unlockedAt).toLocaleString()">{{ new Date(achievement.unlockedAt).getFullYear() }}/{{ new Date(achievement.unlockedAt).getMonth() + 1 }}/{{ new Date(achievement.unlockedAt).getDate() }}</time>
 					</span>
 				</div>
-				<div :class="$style.description">{{ withDescription ? (i18n.ts._achievements._types as any)['_' + achievement.name].description : '???' }}</div>
-				<div v-if="(i18n.ts._achievements._types as any)['_' + achievement.name].flavor && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types as any)['_' + achievement.name].flavor }}</div>
+				<div :class="$style.description">{{ withDescription ? i18n.ts._achievements._types[`_${achievement.name}`].description : '???' }}</div>
+				<div v-if="'flavor' in i18n.ts._achievements._types[`_${achievement.name}`] && withDescription" :class="$style.flavor">{{ (i18n.ts._achievements._types[`_${achievement.name}`] as { flavor: string; }).flavor }}</div>
 			</div>
 		</div>
 		<template v-if="withLocked">
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 5e860351af..bfe66cdf8f 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -45,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts">
 import { markRaw, ref, useTemplateRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
+import * as Misskey from 'misskey-js';
 import sanitizeHtml from 'sanitize-html';
 import { emojilist, getEmojiName } from '@@/js/emojilist.js';
 import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@@/js/emoji-base.js';
@@ -63,7 +64,7 @@ import { prefer } from '@/preferences.js';
 
 export type CompleteInfo = {
 	user: {
-		payload: any;
+		payload: Misskey.entities.User;
 		query: string | null;
 	},
 	hashtag: {
@@ -185,9 +186,9 @@ const suggests = ref<Element>();
 const rootEl = useTemplateRef('rootEl');
 
 const fetching = ref(true);
-const users = ref<any[]>([]);
-const hashtags = ref<any[]>([]);
-const emojis = ref<(EmojiDef)[]>([]);
+const users = ref<Misskey.entities.User[]>([]);
+const hashtags = ref<string[]>([]);
+const emojis = ref<EmojiDef[]>([]);
 const items = ref<Element[] | HTMLCollection>([]);
 const mfmTags = ref<string[]>([]);
 const mfmParams = ref<string[]>([]);
@@ -204,8 +205,8 @@ function complete<T extends keyof CompleteInfo>(type: T, value: CompleteInfo[T][
 	emit('closed');
 	if (type === 'emoji' || type === 'emojiComplete') {
 		let recents = store.s.recentlyUsedEmojis;
-		recents = recents.filter((emoji: any) => emoji !== value);
-		recents.unshift(value);
+		recents = recents.filter((emoji) => emoji !== value);
+		recents.unshift(value as string);
 		store.set('recentlyUsedEmojis', recents.splice(0, 32));
 	}
 }
@@ -254,7 +255,7 @@ function exec() {
 				limit: 10,
 				detail: false,
 			}).then(searchedUsers => {
-				users.value = searchedUsers as any[];
+				users.value = searchedUsers;
 				fetching.value = false;
 				// キャッシュ
 				sessionStorage.setItem(cacheKey, JSON.stringify(searchedUsers));
@@ -276,7 +277,7 @@ function exec() {
 					query: props.q,
 					limit: 30,
 				}).then(searchedHashtags => {
-					hashtags.value = searchedHashtags as any[];
+					hashtags.value = searchedHashtags;
 					fetching.value = false;
 					// キャッシュ
 					sessionStorage.setItem(cacheKey, JSON.stringify(searchedHashtags));
diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue
index 9c0cce7689..3f7eb9bccd 100644
--- a/packages/frontend/src/components/MkExtensionInstaller.vue
+++ b/packages/frontend/src/components/MkExtensionInstaller.vue
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #key>{{ i18n.ts.permission }}</template>
 					<template #value>
 						<ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList">
-							<li v-for="permission in extension.meta.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li>
+							<li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] ?? permission }}</li>
 						</ul>
 						<template v-else>{{ i18n.ts.none }}</template>
 					</template>
@@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</MkFolder>
 	</div>
 
-	<slot name="additionalInfo"/>
+	<slot name="additionalInfo"></slot>
 
 	<div class="_buttonsCenter">
 		<MkButton danger rounded large @click="emits('cancel')"><i class="ti ti-x"></i> {{ i18n.ts.cancel }}</MkButton>
@@ -101,6 +101,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts">
+import * as Misskey from 'misskey-js';
+
 export type Extension = {
 	type: 'plugin';
 	raw: string;
@@ -109,7 +111,7 @@ export type Extension = {
 		version: string;
 		author: string;
 		description?: string;
-		permissions?: string[];
+		permissions?: (typeof Misskey.permissions)[number][];
 		config?: Record<string, unknown>;
 	};
 } | {
@@ -125,7 +127,6 @@ export type Extension = {
 <script lang="ts" setup>
 import { computed } from 'vue';
 import MkButton from '@/components/MkButton.vue';
-import FormSection from '@/components/form/section.vue';
 import FormSplit from '@/components/form/split.vue';
 import MkCode from '@/components/MkCode.vue';
 import MkInfo from '@/components/MkInfo.vue';
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 143ca7fd2f..a22fb7db01 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -121,7 +121,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				{{ notification.invitation.room.name }}
 			</div>
 			<MkA v-else-if="notification.type === 'achievementEarned'" :class="$style.text" to="/my/achievements">
-				{{ (i18n.ts._achievements._types as any)['_' + notification.achievement].title }}
+				{{ i18n.ts._achievements._types[`_${notification.achievement}`].title }}
 			</MkA>
 			<MkA v-else-if="notification.type === 'exportCompleted'" :class="$style.text" :to="`/my/drive/file/${notification.fileId}`">
 				{{ i18n.ts.showFile }}
diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
index 38441b0ea6..cba9b47c56 100644
--- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue
+++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
@@ -144,7 +144,7 @@ async function unsubscribe() {
 }
 
 function encode(buffer: ArrayBuffer | null) {
-	return btoa(String.fromCharCode.apply(null, buffer ? new Uint8Array(buffer) as any : []));
+	return btoa(String.fromCharCode(...(buffer != null ? new Uint8Array(buffer) : [])));
 }
 
 /**
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index 21c20f944b..5b18bab8c9 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, useTemplateRef } from 'vue';
 import { Chart } from 'chart.js';
+import type { ScatterDataPoint } from 'chart.js';
 import tinycolor from 'tinycolor2';
 import { store } from '@/store.js';
 import { useChartTooltip } from '@/composables/use-chart-tooltip.js';
@@ -18,6 +19,12 @@ import { alpha } from '@/utility/color.js';
 import { initChart } from '@/utility/init-chart.js';
 import { misskeyApi } from '@/utility/misskey-api.js';
 
+interface RetentionPoint extends ScatterDataPoint {
+	x: number;
+	y: number;
+	d: string;
+}
+
 initChart();
 
 const chartEl = useTemplateRef('chartEl');
@@ -62,14 +69,14 @@ onMounted(async () => {
 				fill: false,
 				tension: 0.4,
 				data: [{
-					x: '0',
+					x: 0,
 					y: 100,
 					d: getYYYYMMDD(new Date(record.createdAt)),
 				}, ...Object.entries(record.data).sort((a, b) => getDate(a[0]) > getDate(b[0]) ? 1 : -1).map(([k, v], i) => ({
-					x: (i + 1).toString(),
+					x: i + 1,
 					y: (v / record.users) * 100,
 					d: getYYYYMMDD(new Date(record.createdAt)),
-				}))] as any,
+				}))],
 			})),
 		},
 		options: {
@@ -111,11 +118,11 @@ onMounted(async () => {
 					enabled: false,
 					callbacks: {
 						title(context) {
-							const v = context[0].dataset.data[context[0].dataIndex] as unknown as { x: string, y: number, d: string };
+							const v = context[0].dataset.data[context[0].dataIndex] as RetentionPoint;
 							return `${v.x} days later`;
 						},
 						label(context) {
-							const v = context.dataset.data[context.dataIndex] as unknown as { x: string, y: number, d: string };
+							const v = context.dataset.data[context.dataIndex] as RetentionPoint;
 							const p = Math.round(v.y) + '%';
 							return `${v.d} ${p}`;
 						},
diff --git a/packages/frontend/src/components/MkServerSetupWizard.vue b/packages/frontend/src/components/MkServerSetupWizard.vue
index 796c909be9..462ded6de3 100644
--- a/packages/frontend/src/components/MkServerSetupWizard.vue
+++ b/packages/frontend/src/components/MkServerSetupWizard.vue
@@ -245,7 +245,7 @@ const serverSettings = computed<Misskey.entities.AdminUpdateMetaRequest>(() => {
 		enableReactionsBuffering,
 		clientOptions: {
 			entrancePageStyle: q_use.value === 'open' ? 'classic' : 'simple',
-		} as any,
+		},
 	};
 });
 
diff --git a/packages/frontend/src/components/MkWidgetSettingsDialog.vue b/packages/frontend/src/components/MkWidgetSettingsDialog.vue
index 41fec2a9e0..292b4010ff 100644
--- a/packages/frontend/src/components/MkWidgetSettingsDialog.vue
+++ b/packages/frontend/src/components/MkWidgetSettingsDialog.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@ok="save()"
 	@closed="emit('closed')"
 >
-	<template #header><i class="ti ti-icons"></i> {{ (i18n.ts._widgets as any)[widgetName] ?? widgetName }}</template>
+	<template #header><i class="ti ti-icons"></i> {{ i18n.ts._widgets[widgetName] ?? widgetName }}</template>
 
 	<MkPreviewWithControls>
 		<template #preview>
@@ -50,13 +50,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { useTemplateRef, ref, computed, onBeforeUnmount, onMounted } from 'vue';
 import MkPreviewWithControls from './MkPreviewWithControls.vue';
 import type { Form } from '@/utility/form.js';
+import type { WidgetName } from '@/widgets/index.js';
 import { deepClone } from '@/utility/clone.js';
 import { i18n } from '@/i18n.js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkForm from '@/components/MkForm.vue';
 
 const props = defineProps<{
-	widgetName: string;
+	widgetName: WidgetName;
 	form: Form;
 	currentSettings: Record<string, any>;
 }>();
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
index e1fbec4b6f..baa8d783f1 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.vue
+++ b/packages/frontend/src/components/global/MkCondensedLine.vue
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <template>
 <span :class="$style.container">
 	<span ref="content" :class="$style.content" :style="{ maxWidth: `${100 / minScale}%` }">
-		<slot/>
+		<slot></slot>
 	</span>
 </span>
 </template>
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index 547db12e34..6f15826aaa 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -51,9 +51,3 @@ export async function fetchInstance(force = false): Promise<Misskey.entities.Met
 
 	return instance;
 }
-
-export type ClientOptions = {
-	entrancePageStyle: 'classic' | 'simple';
-	showTimelineForVisitor: boolean;
-	showActivitiesForVisitor: boolean;
-};
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index 957526b073..016d1b6a89 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -155,8 +155,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
 import JSON5 from 'json5';
+import * as Misskey from 'misskey-js';
 import { host } from '@@/js/config.js';
-import type { ClientOptions } from '@/instance.js';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import * as os from '@/os.js';
@@ -172,11 +172,11 @@ import MkSwitch from '@/components/MkSwitch.vue';
 const meta = await misskeyApi('admin/meta');
 
 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-const entrancePageStyle = ref<ClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
+const entrancePageStyle = ref<Misskey.entities.MetaClientOptions['entrancePageStyle']>(meta.clientOptions.entrancePageStyle ?? 'classic');
 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-const showTimelineForVisitor = ref<ClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
+const showTimelineForVisitor = ref<Misskey.entities.MetaClientOptions['showTimelineForVisitor']>(meta.clientOptions.showTimelineForVisitor ?? true);
 // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-const showActivitiesForVisitor = ref<ClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
+const showActivitiesForVisitor = ref<Misskey.entities.MetaClientOptions['showActivitiesForVisitor']>(meta.clientOptions.showActivitiesForVisitor ?? true);
 
 const iconUrl = ref(meta.iconUrl);
 const app192IconUrl = ref(meta.app192IconUrl);
@@ -195,11 +195,11 @@ const manifestJsonOverride = ref(meta.manifestJsonOverride === '' ? '{}' : JSON.
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
-		clientOptions: ({
+		clientOptions: {
 			entrancePageStyle: entrancePageStyle.value,
 			showTimelineForVisitor: showTimelineForVisitor.value,
 			showActivitiesForVisitor: showActivitiesForVisitor.value,
-		} as ClientOptions) as any,
+		},
 		iconUrl: iconUrl.value,
 		app192IconUrl: app192IconUrl.value,
 		app512IconUrl: app512IconUrl.value,
diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue
index 6e9f5dd1eb..bc585950b4 100644
--- a/packages/frontend/src/pages/auth.form.vue
+++ b/packages/frontend/src/pages/auth.form.vue
@@ -5,10 +5,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <section>
-	<div v-if="app.permission.length > 0">
+	<div v-if="permissions.length > 0">
 		<p>{{ i18n.tsx._auth.permission({ name }) }}</p>
 		<ul>
-			<li v-for="p in app.permission" :key="p">{{ (i18n.ts._permissions as any)[p] ?? p }}</li>
+			<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] ?? p }}</li>
 		</ul>
 	</div>
 	<div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div>
@@ -37,6 +37,10 @@ const emit = defineEmits<{
 
 const app = computed(() => props.session.app);
 
+const permissions = computed(() => {
+	return props.session.app.permission.filter((p): p is typeof Misskey.permissions[number] => typeof p === 'string');
+});
+
 const name = computed(() => {
 	const el = window.document.createElement('div');
 	el.textContent = app.value.name;
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 43a1e8e426..449f1af60a 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -212,7 +212,7 @@ async function run() {
 	const version = utils.getLangVersion(flash.value.script);
 	const isLegacy = getIsLegacy(version);
 
-	const { Interpreter, Parser, values } = isLegacy ? (await import('@syuilo/aiscript-0-19-0') as any) : await import('@syuilo/aiscript');
+	const { Interpreter, Parser, values } = (isLegacy ? (await import('@syuilo/aiscript-0-19-0')) : await import('@syuilo/aiscript')) as typeof import('@syuilo/aiscript');
 
 	const parser = new Parser();
 
diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue
index 16c764001e..89f457cf69 100644
--- a/packages/frontend/src/pages/settings/plugin.vue
+++ b/packages/frontend/src/pages/settings/plugin.vue
@@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 								<template #key>{{ i18n.ts.permission }}</template>
 								<template #value>
 									<ul style="margin-top: 0; margin-bottom: 0;">
-										<li v-for="permission in plugin.permissions" :key="permission">{{ (i18n.ts._permissions as any)[permission] ?? permission }}</li>
+										<li v-for="permission in plugin.permissions" :key="permission">{{ i18n.ts._permissions[permission] ?? permission }}</li>
 										<li v-if="!plugin.permissions || plugin.permissions.length === 0">{{ i18n.ts.none }}</li>
 									</ul>
 								</template>
diff --git a/packages/frontend/src/pages/settings/preferences.vue b/packages/frontend/src/pages/settings/preferences.vue
index 7d85565ced..1a613466db 100644
--- a/packages/frontend/src/pages/settings/preferences.vue
+++ b/packages/frontend/src/pages/settings/preferences.vue
@@ -1083,7 +1083,7 @@ function removePinnedList() {
 function enableAllDataSaver() {
 	const g = { ...prefer.s.dataSaver };
 
-	Object.keys(g).forEach((key) => { (g as any)[key] = true; });
+	(Object.keys(g) as (keyof typeof g)[]).forEach((key) => { g[key] = true; });
 
 	dataSaver.value = g;
 }
@@ -1091,7 +1091,7 @@ function enableAllDataSaver() {
 function disableAllDataSaver() {
 	const g = { ...prefer.s.dataSaver };
 
-	Object.keys(g).forEach((key) => { (g as any)[key] = false; });
+	(Object.keys(g) as (keyof typeof g)[]).forEach((key) => { g[key] = false; });
 
 	dataSaver.value = g;
 }
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index d33e158ad9..a41d0bc217 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -26,7 +26,7 @@ export type Plugin = {
 	version: string;
 	author?: string;
 	description?: string;
-	permissions?: string[];
+	permissions?: (typeof Misskey.permissions)[number][];
 };
 
 export type AiScriptPluginMeta = {
@@ -34,7 +34,7 @@ export type AiScriptPluginMeta = {
 	version: string;
 	author: string;
 	description?: string;
-	permissions?: string[];
+	permissions?: (typeof Misskey.permissions)[number][];
 	config?: Record<string, any>;
 };
 
@@ -97,7 +97,7 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
 		version: version as string,
 		author: author as string,
 		description: description as string | undefined,
-		permissions: permissions as string[] | undefined,
+		permissions: permissions as (typeof Misskey.permissions)[number][] | undefined,
 		config: config as Record<string, any> | undefined,
 	};
 }
diff --git a/packages/frontend/src/pref-migrate.ts b/packages/frontend/src/pref-migrate.ts
index 7470271627..bf86f6954e 100644
--- a/packages/frontend/src/pref-migrate.ts
+++ b/packages/frontend/src/pref-migrate.ts
@@ -12,6 +12,7 @@ import { deckStore } from '@/ui/deck/deck-store.js';
 import { unisonReload } from '@/utility/unison-reload.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
+import type { SoundStore } from '@/preferences/def.js';
 
 // TODO: そのうち消す
 export function migrateOldSettings() {
@@ -126,10 +127,10 @@ export function migrateOldSettings() {
 		prefer.commit('sound.masterVolume', store.s.sound_masterVolume);
 		prefer.commit('sound.notUseSound', store.s.sound_notUseSound);
 		prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive);
-		prefer.commit('sound.on.note', store.s.sound_note as any);
-		prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
-		prefer.commit('sound.on.notification', store.s.sound_notification as any);
-		prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
+		prefer.commit('sound.on.note', store.s.sound_note as SoundStore);
+		prefer.commit('sound.on.noteMy', store.s.sound_noteMy as SoundStore);
+		prefer.commit('sound.on.notification', store.s.sound_notification as SoundStore);
+		prefer.commit('sound.on.reaction', store.s.sound_reaction as SoundStore);
 		prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility);
 		prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly);
 
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 4674293254..e7cdc98415 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -3,16 +3,12 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { markRaw, ref } from 'vue';
+import { markRaw } from 'vue';
 import * as Misskey from 'misskey-js';
-import lightTheme from '@@/themes/l-light.json5';
-import darkTheme from '@@/themes/d-green-lime.json5';
 import { prefersReducedMotion } from '@@/js/config.js';
 import { hemisphere } from '@@/js/intl-const.js';
 import type { DeviceKind } from '@/utility/device-kind.js';
-import type { Plugin } from '@/plugin.js';
 import type { TIPS } from '@/tips.js';
-import { miLocalStorage } from '@/local-storage.js';
 import { Pizzax } from '@/lib/pizzax.js';
 import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js';
 
diff --git a/packages/frontend/src/utility/autocomplete.ts b/packages/frontend/src/utility/autocomplete.ts
index 4eb1e29d07..a44bf7c1ae 100644
--- a/packages/frontend/src/utility/autocomplete.ts
+++ b/packages/frontend/src/utility/autocomplete.ts
@@ -12,6 +12,15 @@ import { popup } from '@/os.js';
 
 export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
 
+type CompleteProps<T extends keyof CompleteInfo> = {
+	type: T;
+	value: CompleteInfo[T]['payload'];
+};
+
+function isCompleteType<T extends keyof CompleteInfo>(expectedType: T, props: CompleteProps<keyof CompleteInfo>): props is CompleteProps<T> {
+	return props.type === expectedType;
+}
+
 export class Autocomplete {
 	private suggestion: {
 		x: Ref<number>;
@@ -253,19 +262,19 @@ export class Autocomplete {
 	/**
 	 * オートコンプリートする
 	 */
-	private complete<T extends keyof CompleteInfo>({ type, value }: { type: T; value: CompleteInfo[T]['payload'] }) {
+	private complete<T extends keyof CompleteInfo>(props: CompleteProps<T>) {
 		this.close();
 
 		const caret = Number(this.textarea.selectionStart);
 
-		if (type === 'user') {
+		if (isCompleteType('user', props)) {
 			const source = this.text;
 
 			const before = source.substring(0, caret);
 			const trimmedBefore = before.substring(0, before.lastIndexOf('@'));
 			const after = source.substring(caret);
 
-			const acct = value.host === null ? value.username : `${value.username}@${toASCII(value.host)}`;
+			const acct = props.value.host === null ? props.value.username : `${props.value.username}@${toASCII(props.value.host)}`;
 
 			// 挿入
 			this.text = `${trimmedBefore}@${acct} ${after}`;
@@ -276,7 +285,7 @@ export class Autocomplete {
 				const pos = trimmedBefore.length + (acct.length + 2);
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type === 'hashtag') {
+		} else if (isCompleteType('hashtag', props)) {
 			const source = this.text;
 
 			const before = source.substring(0, caret);
@@ -284,15 +293,15 @@ export class Autocomplete {
 			const after = source.substring(caret);
 
 			// 挿入
-			this.text = `${trimmedBefore}#${value} ${after}`;
+			this.text = `${trimmedBefore}#${props.value} ${after}`;
 
 			// キャレットを戻す
 			nextTick(() => {
 				this.textarea.focus();
-				const pos = trimmedBefore.length + (value.length + 2);
+				const pos = trimmedBefore.length + (props.value.length + 2);
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type === 'emoji') {
+		} else if (isCompleteType('emoji', props)) {
 			const source = this.text;
 
 			const before = source.substring(0, caret);
@@ -300,15 +309,15 @@ export class Autocomplete {
 			const after = source.substring(caret);
 
 			// 挿入
-			this.text = trimmedBefore + value + after;
+			this.text = trimmedBefore + props.value + after;
 
 			// キャレットを戻す
 			nextTick(() => {
 				this.textarea.focus();
-				const pos = trimmedBefore.length + value.length;
+				const pos = trimmedBefore.length + props.value.length;
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type === 'emojiComplete') {
+		} else if (isCompleteType('emojiComplete', props)) {
 			const source = this.text;
 
 			const before = source.substring(0, caret);
@@ -316,15 +325,15 @@ export class Autocomplete {
 			const after = source.substring(caret);
 
 			// 挿入
-			this.text = trimmedBefore + value + after;
+			this.text = trimmedBefore + props.value + after;
 
 			// キャレットを戻す
 			nextTick(() => {
 				this.textarea.focus();
-				const pos = trimmedBefore.length + value.length;
+				const pos = trimmedBefore.length + props.value.length;
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type === 'mfmTag') {
+		} else if (isCompleteType('mfmTag', props)) {
 			const source = this.text;
 
 			const before = source.substring(0, caret);
@@ -332,15 +341,15 @@ export class Autocomplete {
 			const after = source.substring(caret);
 
 			// 挿入
-			this.text = `${trimmedBefore}$[${value} ]${after}`;
+			this.text = `${trimmedBefore}$[${props.value} ]${after}`;
 
 			// キャレットを戻す
 			nextTick(() => {
 				this.textarea.focus();
-				const pos = trimmedBefore.length + (value.length + 3);
+				const pos = trimmedBefore.length + (props.value.length + 3);
 				this.textarea.setSelectionRange(pos, pos);
 			});
-		} else if (type === 'mfmParam') {
+		} else if (isCompleteType('mfmParam', props)) {
 			const source = this.text;
 
 			const before = source.substring(0, caret);
@@ -348,12 +357,12 @@ export class Autocomplete {
 			const after = source.substring(caret);
 
 			// 挿入
-			this.text = `${trimmedBefore}.${value}${after}`;
+			this.text = `${trimmedBefore}.${props.value}${after}`;
 
 			// キャレットを戻す
 			nextTick(() => {
 				this.textarea.focus();
-				const pos = trimmedBefore.length + (value.length + 1);
+				const pos = trimmedBefore.length + (props.value.length + 1);
 				this.textarea.setSelectionRange(pos, pos);
 			});
 		}
diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue
index 5dde75e4f2..c2a41b6257 100644
--- a/packages/frontend/src/widgets/WidgetAichan.vue
+++ b/packages/frontend/src/widgets/WidgetAichan.vue
@@ -16,7 +16,7 @@ import { i18n } from '@/i18n.js';
 import type { WidgetComponentProps, WidgetComponentEmits, WidgetComponentExpose } from './widget.js';
 import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
 
-const name = 'ai';
+const name = 'aichan';
 
 const widgetPropsDef = {
 	transparent: {
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index 5d65b001e0..83b8e7ccbc 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -93,12 +93,12 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
 const menuOpened = ref(false);
 
 const headerTitle = computed<string>(() => {
-	if (widgetProps.src === 'list' && widgetProps.list != null) {
-		return widgetProps.list.name;
-	} else if (widgetProps.src === 'antenna' && widgetProps.antenna != null) {
-		return widgetProps.antenna.name;
+	if (widgetProps.src === 'list') {
+		return widgetProps.list != null ? widgetProps.list.name : '?';
+	} else if (widgetProps.src === 'antenna') {
+		return widgetProps.antenna != null ? widgetProps.antenna.name : '?';
 	} else {
-		return (i18n.ts._timelines as any)[widgetProps.src] ?? '?';
+		return i18n.ts._timelines[widgetProps.src] ?? '?';
 	}
 });
 
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index 1984750cd7..498129305b 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -36,7 +36,7 @@ import { misskeyApiGet } from '@/utility/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { prefer } from '@/preferences.js';
 
-const name = 'hashtags';
+const name = 'trends';
 
 const widgetPropsDef = {
 	showHeader: {
diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts
index b3351be45f..79bae68d71 100644
--- a/packages/frontend/src/widgets/index.ts
+++ b/packages/frontend/src/widgets/index.ts
@@ -75,3 +75,5 @@ export const widgets = [
 
 	...federationWidgets,
 ] as const;
+
+export type WidgetName = typeof widgets[number];
diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts
index cab6177247..bfb724ff72 100644
--- a/packages/frontend/src/widgets/widget.ts
+++ b/packages/frontend/src/widgets/widget.ts
@@ -10,6 +10,7 @@ import type { FormWithDefault, GetFormResultType } from '@/utility/form.js';
 import { getDefaultFormValues } from '@/utility/form.js';
 import * as os from '@/os.js';
 import { deepClone } from '@/utility/clone.js';
+import type { WidgetName } from './index.js';
 
 export type Widget<P extends Record<string, unknown>> = {
 	id: string;
@@ -31,7 +32,7 @@ export type WidgetComponentExpose = {
 };
 
 export const useWidgetPropsManager = <F extends FormWithDefault>(
-	name: string,
+	name: WidgetName,
 	propsDef: F,
 	props: Readonly<WidgetComponentProps<GetFormResultType<F>>>,
 	emit: WidgetComponentEmits<GetFormResultType<F>>,
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index ede6025457..b84fdc7e31 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -2230,6 +2230,7 @@ declare namespace entities {
         MetaLite,
         MetaDetailedOnly,
         MetaDetailed,
+        MetaClientOptions,
         UserWebhook,
         SystemWebhook,
         AbuseReportNotificationRecipient,
@@ -2820,6 +2821,9 @@ type MeDetailed = components['schemas']['MeDetailed'];
 // @public (undocumented)
 type MeDetailedOnly = components['schemas']['MeDetailedOnly'];
 
+// @public (undocumented)
+type MetaClientOptions = components['schemas']['MetaClientOptions'];
+
 // @public (undocumented)
 type MetaDetailed = components['schemas']['MetaDetailed'];
 
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 01b48442d6..f45ae39da4 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -58,6 +58,7 @@ export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
 export type MetaLite = components['schemas']['MetaLite'];
 export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly'];
 export type MetaDetailed = components['schemas']['MetaDetailed'];
+export type MetaClientOptions = components['schemas']['MetaClientOptions'];
 export type UserWebhook = components['schemas']['UserWebhook'];
 export type SystemWebhook = components['schemas']['SystemWebhook'];
 export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index dde55e06a0..3aecc765d9 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -5441,7 +5441,7 @@ export type components = {
             feedbackUrl: string | null;
             defaultDarkTheme: string | null;
             defaultLightTheme: string | null;
-            clientOptions: Record<string, never>;
+            clientOptions: components['schemas']['MetaClientOptions'];
             disableRegistration: boolean;
             emailRequiredForSignup: boolean;
             enableHcaptcha: boolean;
@@ -5540,6 +5540,12 @@ export type components = {
             cacheRemoteSensitiveFiles: boolean;
         };
         MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly'];
+        MetaClientOptions: {
+            /** @enum {string} */
+            entrancePageStyle: 'classic' | 'simple';
+            showTimelineForVisitor: boolean;
+            showActivitiesForVisitor: boolean;
+        };
         UserWebhook: {
             /** Format: id */
             id: string;
@@ -9468,7 +9474,7 @@ export interface operations {
                         deeplIsPro: boolean;
                         defaultDarkTheme: string | null;
                         defaultLightTheme: string | null;
-                        clientOptions: Record<string, never>;
+                        clientOptions: components['schemas']['MetaClientOptions'];
                         description: string | null;
                         disableRegistration: boolean;
                         impressumUrl: string | null;
@@ -12724,7 +12730,12 @@ export interface operations {
                     description?: string | null;
                     defaultLightTheme?: string | null;
                     defaultDarkTheme?: string | null;
-                    clientOptions?: Record<string, never>;
+                    clientOptions?: {
+                        /** @enum {string} */
+                        entrancePageStyle?: 'classic' | 'simple';
+                        showTimelineForVisitor?: boolean;
+                        showActivitiesForVisitor?: boolean;
+                    };
                     cacheRemoteFiles?: boolean;
                     cacheRemoteSensitiveFiles?: boolean;
                     emailRequiredForSignup?: boolean;
-- 
cgit v1.2.3-freya

