fix(desktop): show the Lotus titlebar only when the native frame is really gone
CI / Build & Quality Checks (push) Skipped
CI / Secret scan (gitleaks) (push) Skipped
CI / Playwright smoke (e2e) (push) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 23s
CI / Build & Quality Checks (pull_request) Successful in 6m38s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Playwright smoke (e2e) (pull_request) Successful in 30m5s

On Linux / Wayland the desktop can't strip the window frame at runtime,
so turning on custom window chrome drew the Lotus titlebar inside KDE's
frame (two sets of window buttons). The desktop's `set_custom_chrome`
now answers whether the frame is stripped right now; the titlebar and
layout follow that answer (falling back to the setting for older
desktop builds), so there's never two titlebars or none. The setting
says it applies on next start on Linux / Wayland.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
2026-11-09 21:42:27 -05:00
co-authored by Claude Opus 5.5
parent 30b5a7d6af
commit 38871d3e56
5 changed files with 46 additions and 11 deletions
+2 -2
View File
@@ -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;
@@ -160,7 +160,7 @@ function DesktopChromeSetting() {
<SequenceCard className={SequenceCardStyle} variant="SurfaceVariant" direction="Column">
<SettingTile
title="Custom window chrome (beta)"
description="Replace the system title bar with a Lotus-styled one. Desktop only — reloads to apply."
description="Replace the system title bar with a Lotus-styled one. Desktop only — reloads to apply. On Linux (Wayland) it takes effect the next time Lotus Chat starts."
after={<Switch variant="Primary" value={customChrome} onChange={handleToggle} />}
/>
</SequenceCard>
+23 -6
View File
@@ -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 `<TitleBar/>` 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]);
}
+2 -2
View File
@@ -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
+18
View File
@@ -1,3 +1,4 @@
import { atom } from 'jotai';
import {
atomWithLocalStorage,
getLocalStorageItem,
@@ -20,3 +21,20 @@ export const customWindowChromeAtom = atomWithLocalStorage<boolean>(
(key) => getLocalStorageItem<boolean>(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<boolean | null>(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<boolean>(
(get) => get(nativeChromeActiveAtom) ?? get(customWindowChromeAtom),
);