;
+ });
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ describe('suspend', () => {
+ test('should suspend user and update database', async () => {
+ const user = await createUser();
+ const moderator = await createUser();
+
+ await userSuspendService.suspend(user, moderator);
+
+ // ユーザーが凍結されているかチェック
+ const suspendedUser = await usersRepository.findOneBy({ id: user.id });
+ expect(suspendedUser?.isSuspended).toBe(true);
+
+ // モデレーションログが記録されているかチェック
+ expect(moderationLogService.log).toHaveBeenCalledWith(moderator, 'suspend', {
+ userId: user.id,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+ });
+
+ test('should mark follower relationships as suspended', async () => {
+ const user = await createUser();
+ const followee1 = await createUser();
+ const followee2 = await createUser();
+ const moderator = await createUser();
+
+ // ユーザーがフォローしている関係を作成
+ await createFollowing(user, followee1);
+ await createFollowing(user, followee2);
+
+ await userSuspendService.suspend(user, moderator);
+ await setTimeout(250);
+
+ // フォロー関係が論理削除されているかチェック
+ const followings = await followingsRepository.find({
+ where: { followerId: user.id },
+ });
+
+ expect(followings).toHaveLength(2);
+ followings.forEach(following => {
+ expect(following.isFollowerSuspended).toBe(true);
+ });
+ });
+
+ test('should publish internal event for suspension', async () => {
+ const user = await createUser();
+ const moderator = await createUser();
+
+ await userSuspendService.suspend(user, moderator);
+ await setTimeout(250);
+
+ // 内部イベントが発行されているかチェック(非同期処理のため少し待つ)
+ await setTimeout(100);
+
+ expect(globalEventService.publishInternalEvent).toHaveBeenCalledWith(
+ 'userChangeSuspendedState',
+ { id: user.id, isSuspended: true },
+ );
+ });
+ });
+
+ describe('unsuspend', () => {
+ test('should unsuspend user and update database', async () => {
+ const user = await createUser({ isSuspended: true });
+ const moderator = await createUser();
+
+ await userSuspendService.unsuspend(user, moderator);
+ await setTimeout(250);
+
+ // ユーザーの凍結が解除されているかチェック
+ const unsuspendedUser = await usersRepository.findOneBy({ id: user.id });
+ expect(unsuspendedUser?.isSuspended).toBe(false);
+
+ // モデレーションログが記録されているかチェック
+ expect(moderationLogService.log).toHaveBeenCalledWith(moderator, 'unsuspend', {
+ userId: user.id,
+ userUsername: user.username,
+ userHost: user.host,
+ });
+ });
+
+ test('should restore follower relationships', async () => {
+ userEntityService.isSuspendedEither.mockReturnValue(false);
+
+ const user = await createUser({ isSuspended: true });
+ const followee1 = await createUser();
+ const followee2 = await createUser();
+ const moderator = await createUser();
+
+ // 凍結状態のフォロー関係を作成
+ await createFollowing(user, followee1, { isFollowerSuspended: true });
+ await createFollowing(user, followee2, { isFollowerSuspended: true });
+
+ await userSuspendService.unsuspend(user, moderator);
+ await setTimeout(250);
+
+ // フォロー関係が復元されているかチェック
+ const followings = await followingsRepository.find({
+ where: { followerId: user.id },
+ });
+
+ expect(followings).toHaveLength(2);
+ followings.forEach(following => {
+ expect(following.isFollowerSuspended).toBe(false);
+ });
+ });
+
+ test('should publish internal event for unsuspension', async () => {
+ const user = await createUser({ isSuspended: true });
+ const moderator = await createUser();
+
+ await userSuspendService.unsuspend(user, moderator);
+ await setTimeout(250);
+
+ // 内部イベントが発行されているかチェック(非同期処理のため少し待つ)
+ await setTimeout(100);
+
+ expect(globalEventService.publishInternalEvent).toHaveBeenCalledWith(
+ 'userChangeSuspendedState',
+ { id: user.id, isSuspended: false },
+ );
+ });
+ });
+
+ describe('integration test: suspend and unsuspend cycle', () => {
+ test('should preserve follow relationships through suspend/unsuspend cycle', async () => {
+ userEntityService.isSuspendedEither.mockReturnValue(false);
+
+ const user = await createUser();
+ const followee1 = await createUser();
+ const followee2 = await createUser();
+ const moderator = await createUser();
+
+ // 初期のフォロー関係を作成
+ await createFollowing(user, followee1);
+ await createFollowing(user, followee2);
+
+ // 初期状態の確認
+ let followings = await followingsRepository.find({
+ where: { followerId: user.id },
+ });
+ expect(followings).toHaveLength(2);
+ followings.forEach(following => {
+ expect(following.isFollowerSuspended).toBe(false);
+ });
+
+ // 凍結
+ await userSuspendService.suspend(user, moderator);
+ await setTimeout(250);
+
+ // 凍結後の状態確認
+ followings = await followingsRepository.find({
+ where: { followerId: user.id },
+ });
+ expect(followings).toHaveLength(2);
+ followings.forEach(following => {
+ expect(following.isFollowerSuspended).toBe(true);
+ });
+
+ // 凍結解除
+ const suspendedUser = await usersRepository.findOneByOrFail({ id: user.id });
+ await userSuspendService.unsuspend(suspendedUser, moderator);
+ await setTimeout(250);
+
+ // 凍結解除後の状態確認
+ followings = await followingsRepository.find({
+ where: { followerId: user.id },
+ });
+ expect(followings).toHaveLength(2);
+ followings.forEach(following => {
+ expect(following.isFollowerSuspended).toBe(false);
+ });
+ });
+ });
+
+ describe('ActivityPub delivery', () => {
+ test('should deliver Update Person activity on suspend of local user', async () => {
+ const localUser = await createUser({ host: null });
+ const moderator = await createUser();
+
+ userEntityService.isLocalUser.mockReturnValue(true);
+ userEntityService.genLocalUserUri.mockReturnValue(`https://example.com/users/${localUser.id}`);
+ apRendererService.renderUpdate.mockReturnValue({ type: 'Update' } as any);
+ apRendererService.renderPerson.mockReturnValue({ type: 'Person' } as any);
+ apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Update' } as any);
+
+ await userSuspendService.suspend(localUser, moderator);
+ await setTimeout(250);
+
+ // ActivityPub配信が呼ばれているかチェック
+ expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser);
+ expect(apRendererService.renderUpdate).toHaveBeenCalled();
+ expect(apRendererService.renderPerson).toHaveBeenCalled();
+ expect(apRendererService.addContext).toHaveBeenCalled();
+ expect(queueService.deliverMany).toHaveBeenCalled();
+ });
+
+ test('should deliver Update Person activity on unsuspend of local user', async () => {
+ const localUser = await createUser({ host: null, isSuspended: true });
+ const moderator = await createUser();
+
+ userEntityService.isLocalUser.mockReturnValue(true);
+ userEntityService.genLocalUserUri.mockReturnValue(`https://example.com/users/${localUser.id}`);
+ apRendererService.renderUpdate.mockReturnValue({ type: 'Update' } as any);
+ apRendererService.renderPerson.mockReturnValue({ type: 'Person' } as any);
+ apRendererService.addContext.mockReturnValue({ '@context': '...', type: 'Update' } as any);
+
+ await userSuspendService.suspend(localUser, moderator);
+ await setTimeout(250);
+
+ // ActivityPub配信が呼ばれているかチェック
+ expect(userEntityService.isLocalUser).toHaveBeenCalledWith(localUser);
+ expect(apRendererService.renderUpdate).toHaveBeenCalled();
+ expect(apRendererService.renderPerson).toHaveBeenCalled();
+ expect(apRendererService.addContext).toHaveBeenCalled();
+ expect(queueService.deliverMany).toHaveBeenCalled();
+ });
+
+ test('should not deliver any activity on suspend of remote user', async () => {
+ const remoteUser = await createUser({ host: 'remote.example.com' });
+ const moderator = await createUser();
+
+ userEntityService.isLocalUser.mockReturnValue(false);
+
+ await userSuspendService.suspend(remoteUser, moderator);
+ await setTimeout(250);
+
+ // ActivityPub配信が呼ばれていないことをチェック
+ expect(userEntityService.isLocalUser).toHaveBeenCalledWith(remoteUser);
+ expect(apRendererService.renderDelete).not.toHaveBeenCalled();
+ expect(queueService.deliver).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('suspension for remote user', () => {
+ test('should suspend remote user without AP delivery', async () => {
+ const remoteUser = await createUser({ host: genHost() });
+ const moderator = await createUser();
+
+ await userSuspendService.suspend(remoteUser, moderator);
+ await setTimeout(250);
+
+ // ユーザーが凍結されているかチェック
+ const suspendedUser = await usersRepository.findOneBy({ id: remoteUser.id });
+ expect(suspendedUser?.isSuspended).toBe(true);
+
+ // モデレーションログが記録されているかチェック
+ expect(moderationLogService.log).toHaveBeenCalledWith(moderator, 'suspend', {
+ userId: remoteUser.id,
+ userUsername: remoteUser.username,
+ userHost: remoteUser.host,
+ });
+
+ // ActivityPub配信が呼ばれていないことを確認
+ expect(queueService.deliver).not.toHaveBeenCalled();
+ });
+
+ test('should unsuspend remote user without AP delivery', async () => {
+ const remoteUser = await createUser({ host: genHost(), isSuspended: true });
+ const moderator = await createUser();
+
+ await userSuspendService.unsuspend(remoteUser, moderator);
+
+ await setTimeout(250);
+
+ // ユーザーの凍結が解除されているかチェック
+ const unsuspendedUser = await usersRepository.findOneBy({ id: remoteUser.id });
+ expect(unsuspendedUser?.isSuspended).toBe(false);
+
+ // モデレーションログが記録されているかチェック
+ expect(moderationLogService.log).toHaveBeenCalledWith(moderator, 'unsuspend', {
+ userId: remoteUser.id,
+ userUsername: remoteUser.username,
+ userHost: remoteUser.host,
+ });
+
+ // ActivityPub配信が呼ばれていないことを確認
+ expect(queueService.deliver).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('suspension from remote', () => {
+ test('should suspend remote user and post suspend event', async () => {
+ const remoteUser = await createUser({ host: genHost() }) as MiRemoteUser;
+ await userSuspendService.suspendFromRemote(remoteUser);
+
+ // ユーザーがリモート凍結されているかチェック
+ const suspendedUser = await usersRepository.findOneBy({ id: remoteUser.id });
+ expect(suspendedUser?.isRemoteSuspended).toBe(true);
+
+ // イベントが発行されているかチェック
+ expect(globalEventService.publishInternalEvent).toHaveBeenCalledWith(
+ 'userChangeSuspendedState',
+ { id: remoteUser.id, isRemoteSuspended: true },
+ );
+ });
+
+ test('should unsuspend remote user and post unsuspend event', async () => {
+ const remoteUser = await createUser({ host: genHost(), isRemoteSuspended: true }) as MiRemoteUser;
+ await userSuspendService.unsuspendFromRemote(remoteUser);
+
+ // ユーザーのリモート凍結が解除されているかチェック
+ const unsuspendedUser = await usersRepository.findOneBy({ id: remoteUser.id });
+ expect(unsuspendedUser?.isRemoteSuspended).toBe(false);
+
+ // イベントが発行されているかチェック
+ expect(globalEventService.publishInternalEvent).toHaveBeenCalledWith(
+ 'userChangeSuspendedState',
+ { id: remoteUser.id, isRemoteSuspended: false },
+ );
+ });
+ });
+});
diff --git a/packages/backend/test/unit/entities/UserEntityService.ts b/packages/backend/test/unit/entities/UserEntityService.ts
index ca6a639be8..f2b40e83a8 100644
--- a/packages/backend/test/unit/entities/UserEntityService.ts
+++ b/packages/backend/test/unit/entities/UserEntityService.ts
@@ -51,6 +51,7 @@ import { ReactionService } from '@/core/ReactionService.js';
import { NotificationService } from '@/core/NotificationService.js';
import { ReactionsBufferingService } from '@/core/ReactionsBufferingService.js';
import { ChatService } from '@/core/ChatService.js';
+import { UserSuspendService } from '@/core/UserSuspendService.js';
process.env.NODE_ENV = 'test';
@@ -170,6 +171,7 @@ describe('UserEntityService', () => {
InstanceChart,
ApLoggerService,
AccountMoveService,
+ UserSuspendService,
ReactionService,
ReactionsBufferingService,
NotificationService,
diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json
index 5e73c26daf..bae5b0ebd3 100644
--- a/packages/frontend-embed/package.json
+++ b/packages/frontend-embed/package.json
@@ -13,10 +13,10 @@
"@discordapp/twemoji": "15.1.0",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2",
- "@rollup/pluginutils": "5.1.4",
+ "@rollup/pluginutils": "5.2.0",
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.4",
- "@vue/compiler-sfc": "3.5.16",
+ "@vue/compiler-sfc": "3.5.17",
"astring": "1.9.0",
"buraha": "0.0.1",
"estree-walker": "3.0.3",
@@ -26,46 +26,46 @@
"mfm-js": "0.24.0",
"misskey-js": "workspace:*",
"punycode.js": "2.3.1",
- "rollup": "4.42.0",
+ "rollup": "4.45.1",
"sass": "1.89.2",
- "shiki": "3.6.0",
+ "shiki": "3.8.0",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.16",
"tsconfig-paths": "4.2.0",
"typescript": "5.8.3",
"uuid": "11.1.0",
"vite": "6.3.5",
- "vue": "3.5.16"
+ "vue": "3.5.17"
},
"devDependencies": {
- "@misskey-dev/summaly": "5.2.1",
+ "@misskey-dev/summaly": "5.2.2",
"@tabler/icons-webfont": "3.34.0",
"@testing-library/vue": "8.1.0",
"@types/estree": "1.0.8",
"@types/micromatch": "4.0.9",
- "@types/node": "22.15.31",
+ "@types/node": "22.16.4",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1",
- "@typescript-eslint/eslint-plugin": "8.34.0",
- "@typescript-eslint/parser": "8.34.0",
- "@vitest/coverage-v8": "3.2.3",
- "@vue/runtime-core": "3.5.16",
+ "@typescript-eslint/eslint-plugin": "8.37.0",
+ "@typescript-eslint/parser": "8.37.0",
+ "@vitest/coverage-v8": "3.2.4",
+ "@vue/runtime-core": "3.5.17",
"acorn": "8.15.0",
"cross-env": "7.0.3",
- "eslint-plugin-import": "2.31.0",
- "eslint-plugin-vue": "10.2.0",
+ "eslint-plugin-import": "2.32.0",
+ "eslint-plugin-vue": "10.3.0",
"fast-glob": "3.3.3",
"happy-dom": "17.6.3",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
- "msw": "2.10.2",
+ "msw": "2.10.4",
"nodemon": "3.1.10",
- "prettier": "3.5.3",
+ "prettier": "3.6.2",
"start-server-and-test": "2.0.12",
"vite-plugin-turbosnap": "1.0.3",
- "vue-component-type-helpers": "2.2.10",
- "vue-eslint-parser": "10.1.3",
- "vue-tsc": "2.2.10"
+ "vue-component-type-helpers": "2.2.12",
+ "vue-eslint-parser": "10.2.0",
+ "vue-tsc": "2.2.12"
}
}
diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts
index 4498a5e2b2..5c33c38f44 100644
--- a/packages/frontend-shared/js/const.ts
+++ b/packages/frontend-shared/js/const.ts
@@ -112,6 +112,7 @@ export const ROLE_POLICIES = [
'chatAvailability',
'uploadableFileTypes',
'noteDraftLimit',
+ 'watermarkAvailable',
] as const;
export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json
index bd9bbbe7b6..7caa7fd283 100644
--- a/packages/frontend-shared/package.json
+++ b/packages/frontend-shared/package.json
@@ -21,20 +21,20 @@
"lint": "pnpm typecheck && pnpm eslint"
},
"devDependencies": {
- "@types/node": "22.15.31",
- "@typescript-eslint/eslint-plugin": "8.34.0",
- "@typescript-eslint/parser": "8.34.0",
- "esbuild": "0.25.5",
- "eslint-plugin-vue": "10.2.0",
+ "@types/node": "22.16.4",
+ "@typescript-eslint/eslint-plugin": "8.37.0",
+ "@typescript-eslint/parser": "8.37.0",
+ "esbuild": "0.25.6",
+ "eslint-plugin-vue": "10.3.0",
"nodemon": "3.1.10",
"typescript": "5.8.3",
- "vue-eslint-parser": "10.1.3"
+ "vue-eslint-parser": "10.2.0"
},
"files": [
"js-built"
],
"dependencies": {
"misskey-js": "workspace:*",
- "vue": "3.5.16"
+ "vue": "3.5.17"
}
}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 0dbd94362b..b8964549f4 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -23,19 +23,19 @@
"@misskey-dev/browser-image-resizer": "2024.1.0",
"@rollup/plugin-json": "6.1.0",
"@rollup/plugin-replace": "6.0.2",
- "@rollup/pluginutils": "5.1.4",
- "@sentry/vue": "9.27.0",
+ "@rollup/pluginutils": "5.2.0",
+ "@sentry/vue": "9.39.0",
"@syuilo/aiscript": "0.19.0",
"@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.4",
- "@vue/compiler-sfc": "3.5.16",
+ "@vue/compiler-sfc": "3.5.17",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"analytics": "0.8.16",
"astring": "1.9.0",
"broadcast-channel": "7.1.0",
"buraha": "0.0.1",
"canvas-confetti": "1.9.3",
- "chart.js": "4.4.9",
+ "chart.js": "4.5.0",
"chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "2.1.1",
"chartjs-plugin-gradient": "0.6.1",
@@ -60,13 +60,13 @@
"misskey-reversi": "workspace:*",
"photoswipe": "5.4.4",
"punycode.js": "2.3.1",
- "rollup": "4.42.0",
+ "rollup": "4.45.1",
"sanitize-html": "2.17.0",
"sass": "1.89.2",
- "shiki": "3.6.0",
+ "shiki": "3.8.0",
"strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0",
- "three": "0.177.0",
+ "three": "0.178.0",
"throttle-debounce": "5.0.2",
"tinycolor2": "1.6.0",
"tsc-alias": "1.8.16",
@@ -74,12 +74,12 @@
"typescript": "5.8.3",
"v-code-diff": "1.13.1",
"vite": "6.3.5",
- "vue": "3.5.16",
+ "vue": "3.5.17",
"vuedraggable": "next",
"wanakana": "5.3.1"
},
"devDependencies": {
- "@misskey-dev/summaly": "5.2.1",
+ "@misskey-dev/summaly": "5.2.2",
"@storybook/addon-actions": "8.6.14",
"@storybook/addon-essentials": "8.6.14",
"@storybook/addon-interactions": "8.6.14",
@@ -104,32 +104,32 @@
"@types/estree": "1.0.8",
"@types/matter-js": "0.19.8",
"@types/micromatch": "4.0.9",
- "@types/node": "22.15.31",
+ "@types/node": "22.16.4",
"@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.16.0",
"@types/seedrandom": "3.0.8",
"@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.1",
- "@typescript-eslint/eslint-plugin": "8.34.0",
- "@typescript-eslint/parser": "8.34.0",
- "@vitest/coverage-v8": "3.2.3",
- "@vue/compiler-core": "3.5.16",
- "@vue/runtime-core": "3.5.16",
+ "@typescript-eslint/eslint-plugin": "8.37.0",
+ "@typescript-eslint/parser": "8.37.0",
+ "@vitest/coverage-v8": "3.2.4",
+ "@vue/compiler-core": "3.5.17",
+ "@vue/runtime-core": "3.5.17",
"acorn": "8.15.0",
"cross-env": "7.0.3",
- "cypress": "14.4.1",
- "eslint-plugin-import": "2.31.0",
- "eslint-plugin-vue": "10.2.0",
+ "cypress": "14.5.2",
+ "eslint-plugin-import": "2.32.0",
+ "eslint-plugin-vue": "10.3.0",
"fast-glob": "3.3.3",
"happy-dom": "17.6.3",
"intersection-observer": "0.12.2",
"micromatch": "4.0.8",
- "minimatch": "10.0.1",
- "msw": "2.10.2",
+ "minimatch": "10.0.3",
+ "msw": "2.10.4",
"msw-storybook-addon": "2.0.5",
"nodemon": "3.1.10",
- "prettier": "3.5.3",
+ "prettier": "3.6.2",
"react": "19.1.0",
"react-dom": "19.1.0",
"seedrandom": "3.0.5",
@@ -137,10 +137,10 @@
"storybook": "8.6.14",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-plugin-turbosnap": "1.0.3",
- "vitest": "3.2.3",
+ "vitest": "3.2.4",
"vitest-fetch-mock": "0.4.5",
- "vue-component-type-helpers": "2.2.10",
- "vue-eslint-parser": "10.1.3",
- "vue-tsc": "2.2.10"
+ "vue-component-type-helpers": "2.2.12",
+ "vue-eslint-parser": "10.2.0",
+ "vue-tsc": "2.2.12"
}
}
diff --git a/packages/frontend/src/components/MkNoteDraftsDialog.vue b/packages/frontend/src/components/MkNoteDraftsDialog.vue
index 7d41740264..5b8211b715 100644
--- a/packages/frontend/src/components/MkNoteDraftsDialog.vue
+++ b/packages/frontend/src/components/MkNoteDraftsDialog.vue
@@ -42,6 +42,13 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+ {{ i18n.ts.deletedNote }}
+
+
+
@@ -50,6 +57,13 @@ SPDX-License-Identifier: AGPL-3.0-only
+
+
+
+ {{ i18n.ts.deletedNote }}
+
+
+
{{ i18n.tsx._drafts.postTo({ channel: draft.channel.name }) }}
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index 98247f5d0f..c792ff3488 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -69,13 +69,13 @@ function getScreenY(event: TouchEvent | MouseEvent | PointerEvent): number {
function lockDownScroll() {
if (scrollEl == null) return;
scrollEl.style.touchAction = 'pan-x pan-down pinch-zoom';
- scrollEl.style.overscrollBehavior = 'none';
+ scrollEl.style.overscrollBehavior = 'auto none';
}
function unlockDownScroll() {
if (scrollEl == null) return;
scrollEl.style.touchAction = 'auto';
- scrollEl.style.overscrollBehavior = 'contain';
+ scrollEl.style.overscrollBehavior = 'auto contain';
}
function moveStartByMouse(event: MouseEvent) {
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index aebec7a8f6..0f8713d4af 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
-