diff --git a/src/app/features/desktop/TitleBar.tsx b/src/app/features/desktop/TitleBar.tsx
index 41f3d379f..52ac44b7e 100644
--- a/src/app/features/desktop/TitleBar.tsx
+++ b/src/app/features/desktop/TitleBar.tsx
@@ -1,7 +1,7 @@
import React, { MouseEvent, ReactNode } from 'react';
import { useAtomValue } from 'jotai';
import { Text } from 'folds';
-import { customWindowChromeAtom } from '../../state/customWindowChrome';
+import { customChromeShownAtom } from '../../state/customWindowChrome';
import { invokeTauri, isTauri } from '../../hooks/useTauri';
import * as css from './TitleBar.css';
@@ -75,7 +75,7 @@ function ControlButton({ label, glyph, onClick, close }: ControlButtonProps) {
* real traffic lights — are stripped while custom chrome is on.
*/
export function TitleBar() {
- const enabled = useAtomValue(customWindowChromeAtom);
+ const enabled = useAtomValue(customChromeShownAtom);
if (!isTauri() || !enabled) return null;
diff --git a/src/app/features/settings/general/General.tsx b/src/app/features/settings/general/General.tsx
index 1767dc246..892321e06 100644
--- a/src/app/features/settings/general/General.tsx
+++ b/src/app/features/settings/general/General.tsx
@@ -160,7 +160,7 @@ function DesktopChromeSetting() {
}
/>
diff --git a/src/app/hooks/useTauriWindowChrome.ts b/src/app/hooks/useTauriWindowChrome.ts
index 0f42c6bf1..28ce34361 100644
--- a/src/app/hooks/useTauriWindowChrome.ts
+++ b/src/app/hooks/useTauriWindowChrome.ts
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
-import { useAtomValue } from 'jotai';
-import { customWindowChromeAtom } from '../state/customWindowChrome';
-import { invokeTauri, isTauri } from './useTauri';
+import { useAtomValue, useSetAtom } from 'jotai';
+import { customWindowChromeAtom, nativeChromeActiveAtom } from '../state/customWindowChrome';
+import { isTauri, tauriInvoke } from './useTauri';
/**
* P5-47 — drive the native window frame from the `customWindowChromeAtom`.
@@ -11,12 +11,29 @@ import { invokeTauri, isTauri } from './useTauri';
* web `` can take over, `enabled = false` restores the native frame.
* No-op in the browser (`isTauri()` guard), so it's safe to call unconditionally
* from the app shell.
+ *
+ * The command answers whether the native frame is stripped *now* (on Linux /
+ * Wayland the frame only changes at the next launch); that drives whether the
+ * Lotus titlebar is shown. Older desktop builds answer nothing, so the setting
+ * is used as before.
*/
export function useTauriWindowChrome(): void {
const enabled = useAtomValue(customWindowChromeAtom);
+ const setNativeActive = useSetAtom(nativeChromeActiveAtom);
useEffect(() => {
- if (!isTauri()) return;
- invokeTauri('set_custom_chrome', { enabled });
- }, [enabled]);
+ const invoke = isTauri() ? tauriInvoke() : undefined;
+ if (!invoke) return undefined;
+ let cancelled = false;
+ invoke('set_custom_chrome', { enabled })
+ .then((active) => {
+ if (!cancelled) setNativeActive(typeof active === 'boolean' ? active : enabled);
+ })
+ .catch(() => {
+ if (!cancelled) setNativeActive(enabled);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, [enabled, setNativeActive]);
}
diff --git a/src/app/pages/App.tsx b/src/app/pages/App.tsx
index c005066b3..79b519408 100644
--- a/src/app/pages/App.tsx
+++ b/src/app/pages/App.tsx
@@ -31,7 +31,7 @@ import { useSessionAppeared } from '../hooks/useSessionAppeared';
import { useTauriWindowChrome } from '../hooks/useTauriWindowChrome';
import { isTauri } from '../hooks/useTauri';
import { TitleBar } from '../features/desktop/TitleBar';
-import { customWindowChromeAtom } from '../state/customWindowChrome';
+import { customChromeShownAtom } from '../state/customWindowChrome';
import { SeasonalEffect } from '../components/seasonal/SeasonalEffect';
import { applyCustomAccent, removeCustomAccent } from '../utils/accentColor';
import { zIndices } from '../styles/zIndex';
@@ -110,7 +110,7 @@ function TauriEffects() {
// default, and always in the browser) this returns children unchanged, so there
// is zero layout impact for everyone else.
function DesktopChrome({ children }: { children: ReactNode }) {
- const customChrome = useAtomValue(customWindowChromeAtom);
+ const customChrome = useAtomValue(customChromeShownAtom);
useTauriWindowChrome();
const useChrome = isTauri() && customChrome;
// Keep the wrapper element structure STABLE across the toggle so flipping the
diff --git a/src/app/state/customWindowChrome.ts b/src/app/state/customWindowChrome.ts
index ea8d3f7a3..ab0fdb642 100644
--- a/src/app/state/customWindowChrome.ts
+++ b/src/app/state/customWindowChrome.ts
@@ -1,3 +1,4 @@
+import { atom } from 'jotai';
import {
atomWithLocalStorage,
getLocalStorageItem,
@@ -20,3 +21,20 @@ export const customWindowChromeAtom = atomWithLocalStorage(
(key) => getLocalStorageItem(key, false),
(key, value) => setLocalStorageItem(key, value),
);
+
+/**
+ * [cinny-desktop: Linux custom chrome] Whether the native frame is **actually**
+ * stripped right now, as reported by the desktop's `set_custom_chrome`. It can
+ * differ from the setting: on Linux / Wayland the frame only changes when the
+ * app restarts. `null` until the desktop has answered (or on desktop builds
+ * that don't report it).
+ */
+export const nativeChromeActiveAtom = atom(null);
+
+/**
+ * Show the Lotus titlebar only when the native frame is really gone (never two
+ * titlebars, never none): the desktop's report, else the setting.
+ */
+export const customChromeShownAtom = atom(
+ (get) => get(nativeChromeActiveAtom) ?? get(customWindowChromeAtom),
+);