diff --git a/packages/frontend/src/components/MkRoleSelectDialog.vue b/packages/frontend/src/components/MkRoleSelectDialog.vue
index 229fd9d1ba..5f77dc6734 100644
--- a/packages/frontend/src/components/MkRoleSelectDialog.vue
+++ b/packages/frontend/src/components/MkRoleSelectDialog.vue
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:width="400"
 	:height="500"
 	@close="onCloseModalWindow"
-	@closed="console.log('MkRoleSelectDialog: closed') ; $emit('dispose')"
+	@closed="emit('closed')"
 >
 	<template #header>{{ title }}</template>
 	<MkSpacer :marginMin="20" :marginMax="28">
@@ -58,7 +58,7 @@ import MkLoading from '@/components/global/MkLoading.vue';
 const emit = defineEmits<{
 	(ev: 'done', value: Misskey.entities.Role[]),
 	(ev: 'close'),
-	(ev: 'dispose'),
+	(ev: 'closed'),
 }>();
 
 const props = withDefaults(defineProps<{
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index 8e4c97e59f..eed929432c 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -21,10 +21,10 @@ import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
 import MkPageWindow from '@/components/MkPageWindow.vue';
 import MkToast from '@/components/MkToast.vue';
 import MkDialog from '@/components/MkDialog.vue';
-import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
-import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
 import MkPopupMenu from '@/components/MkPopupMenu.vue';
 import MkContextMenu from '@/components/MkContextMenu.vue';
+import type MkRoleSelectDialog_TypeReferenceOnly from '@/components/MkRoleSelectDialog.vue';
+import type MkEmojiPickerDialog_TypeReferenceOnly from '@/components/MkEmojiPickerDialog.vue';
 import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
 import { pleaseLogin } from '@/utility/please-login.js';
 import { showMovedDialog } from '@/utility/show-moved-dialog.js';
@@ -181,7 +181,7 @@ type EmitsExtractor<T> = {
 export function popup<T extends Component>(
 	component: T,
 	props: ComponentProps<T>,
-	events: ComponentEmit<T> = {} as ComponentEmit<T>,
+	events: Partial<ComponentEmit<T>> = {},
 ): { dispose: () => void } {
 	markRaw(component);
 
@@ -460,7 +460,7 @@ export function authenticateDialog(): Promise<{
 	canceled: false; result: { password: string; token: string | null; };
 }> {
 	return new Promise(resolve => {
-		const { dispose } = popup(MkPasswordDialog, {}, {
+		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkPasswordDialog.vue')), {}, {
 			done: result => {
 				resolve(result ? { canceled: false, result } : { canceled: true, result: undefined });
 			},
@@ -617,30 +617,26 @@ export async function selectDriveFolder(multiple: boolean): Promise<Misskey.enti
 	});
 }
 
-export async function selectRole(params: {
-	initialRoleIds?: string[],
-	title?: string,
-	infoMessage?: string,
-	publicOnly?: boolean,
-}): Promise<
+export async function selectRole(params: ComponentProps<typeof MkRoleSelectDialog_TypeReferenceOnly>): Promise<
 	{ canceled: true; result: undefined; } |
 	{ canceled: false; result: Misskey.entities.Role[] }
-	> {
+> {
 	return new Promise((resolve) => {
-		popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
+		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkRoleSelectDialog.vue')), params, {
 			done: roles => {
 				resolve({ canceled: false, result: roles });
 			},
 			close: () => {
 				resolve({ canceled: true, result: undefined });
 			},
-		}, 'dispose');
+			closed: () => dispose(),
+		});
 	});
 }
 
-export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog>): Promise<string> {
+export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog_TypeReferenceOnly>): Promise<string> {
 	return new Promise(resolve => {
-		const { dispose } = popup(MkEmojiPickerDialog, {
+		const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), {
 			src,
 			...opts,
 		}, {
@@ -675,6 +671,10 @@ export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | n
 	width?: number;
 	onClosing?: () => void;
 }): Promise<void> {
+	if (!(src instanceof HTMLElement)) {
+		src = null;
+	}
+
 	let returnFocusTo = getHTMLElementOrNull(src) ?? getHTMLElementOrNull(document.activeElement);
 	return new Promise(resolve => nextTick(() => {
 		const { dispose } = popup(MkPopupMenu, {