setMessage(null)} />
+
{updates && noticeCount > 0 && (
diff --git a/components/settings/__tests__/no-native-dialogs.test.ts b/components/settings/__tests__/no-native-dialogs.test.ts
new file mode 100644
index 000000000..303453740
--- /dev/null
+++ b/components/settings/__tests__/no-native-dialogs.test.ts
@@ -0,0 +1,40 @@
+import { readdirSync, readFileSync, statSync } from 'node:fs';
+import { join, relative } from 'node:path';
+import { describe, expect, it } from 'vitest';
+
+// R4: no window.confirm, alert() or prompt() in Settings and the admin
+// panel. Questions go through ConfirmDialog (the useConfirmDialog hook's
+// confirm({ ... }) is fine), results through toasts.
+
+const ROOT = join(__dirname, '..', '..', '..');
+const DIRS = ['components/settings', 'components/admin', 'components/filters', 'components/identity', 'app/(main)/admin'];
+
+function sources(dir: string): string[] {
+ return readdirSync(dir).flatMap((name) => {
+ const path = join(dir, name);
+ if (statSync(path).isDirectory()) return name === '__tests__' ? [] : sources(path);
+ return /\.tsx?$/.test(name) ? [path] : [];
+ });
+}
+
+const NATIVE = [
+ /\bwindow\.(confirm|alert|prompt)\s*\(/,
+ /(? {
+ it('are not used', () => {
+ const found: string[] = [];
+ for (const dir of DIRS) {
+ for (const file of sources(join(ROOT, dir))) {
+ readFileSync(file, 'utf8').split('\n').forEach((line, i) => {
+ if (line.trim().startsWith('//') || line.trim().startsWith('*')) return;
+ if (NATIVE.some((re) => re.test(line))) found.push(`${relative(ROOT, file)}:${i + 1}: ${line.trim()}`);
+ });
+ }
+ }
+ expect(found).toEqual([]);
+ });
+});