diff --git a/src/features/settings/__tests__/dialog.test.tsx b/src/features/settings/__tests__/dialog.test.tsx
index a72f4752..a187bf41 100644
--- a/src/features/settings/__tests__/dialog.test.tsx
+++ b/src/features/settings/__tests__/dialog.test.tsx
@@ -22,6 +22,7 @@ vi.mock("@/platform/ipc", async (importOriginal) => {
});
import { registerAction, executeAction } from "@/platform/actions";
+import { usePlatform } from "@/platform/platformInfo";
import { useProject } from "@/platform/project";
import { useKeymap } from "@/platform/keymap";
import { ContextMenuHost } from "@/ui";
@@ -29,6 +30,8 @@ import { PageContext, useSetting } from "../components/context";
import { DEFAULT_SETTINGS, setAt } from "../model/schema";
import { openSettings, stage, useSettingsDialog } from "../model/staging";
import { SettingsDialog } from "../dialog/SettingsDialog";
+import { SettingsWindow } from "../window/SettingsWindow";
+import dialogStyles from "../dialog/SettingsDialog.module.css";
import { activate } from "..";
import { MessagesHost } from "@/workbench/messages";
@@ -471,6 +474,23 @@ describe("Settings dialog", () => {
});
});
+describe("native Settings window root", () => {
+ it("marks the window edge on Linux, whose frameless windows get no compositor shadow", () => {
+ const saved = usePlatform.getState();
+ usePlatform.setState({ os: "linux" });
+ let view = render();
+ let root = document.querySelector(`.${dialogStyles.window}`);
+ expect(root).toHaveClass(dialogStyles.windowEdge!);
+ view.unmount();
+ usePlatform.setState({ os: "windows" });
+ view = render();
+ root = document.querySelector(`.${dialogStyles.window}`);
+ expect(root).not.toHaveClass(dialogStyles.windowEdge!);
+ view.unmount();
+ usePlatform.setState(saved);
+ });
+});
+
describe("native Settings window background", () => {
it.each([
["a hex frame", "#26282c", "", "#26282c"],
diff --git a/src/features/settings/dialog/SettingsDialog.module.css b/src/features/settings/dialog/SettingsDialog.module.css
index 24fdea93..0a1105ab 100644
--- a/src/features/settings/dialog/SettingsDialog.module.css
+++ b/src/features/settings/dialog/SettingsDialog.module.css
@@ -203,6 +203,11 @@
background: var(--bg-dialog, var(--bg-elev));
color: var(--fg);
}
+/* Linux draws no shadow for a frameless window (Wayland/KDE give undecorated
+ windows no CSD), so mark its edge in CSS instead. */
+.windowEdge {
+ box-shadow: inset 0 0 0 1px var(--border-window);
+}
.windowBody {
flex: 1;
min-height: 0;
diff --git a/src/features/settings/window/SettingsWindow.tsx b/src/features/settings/window/SettingsWindow.tsx
index e5509490..231a6064 100644
--- a/src/features/settings/window/SettingsWindow.tsx
+++ b/src/features/settings/window/SettingsWindow.tsx
@@ -1,4 +1,5 @@
/** Content of the native Settings window: title bar, page tree + page, buttons. */
+import clsx from "clsx";
import { useEffect, useRef } from "react";
import { OverlayHost } from "@/workbench/OverlayHost";
import { usePlatform } from "@/platform/platformInfo";
@@ -10,6 +11,7 @@ import styles from "../dialog/SettingsDialog.module.css";
export function SettingsWindow() {
useApplyTheme();
+ const os = usePlatform((s) => s.os);
const ownTitleBar = usePlatform((s) => s.overlayTitleBar || s.framelessTitleBar);
const open = useSettingsDialog((s) => s.open);
const title = useSettingsTitle();
@@ -20,8 +22,8 @@ export function SettingsWindow() {
}, [open]);
return (
-
- {/* macOS overlays traffic lights on it; Windows is frameless. Linux keeps the OS title bar. */}
+
+ {/* macOS overlays traffic lights on it; Windows and Linux are frameless (tauri..conf.json). */}
{ownTitleBar && }
{open && (
<>