Files
cinny/src/app/components/CloseBehaviorPrompt.tsx
T
Lotus CIandClaude Opus 5.5 37b3ae1ad7
CI / Build & Quality Checks (pull_request) Successful in 3m13s
CI / Trigger Desktop Build (pull_request) Skipped
CI / Docker image build & smoke test (pull_request) Skipped
CI / Secret scan (gitleaks) (pull_request) Successful in 13s
CI / Playwright smoke (e2e) (pull_request) Successful in 18m14s
fix(desktop): without a tray, the first-close dialog defaults to Quit (cinny-desktop #37)
On desktops with no tray host (stock GNOME), "Keep running in the tray"
hid the window with no icon to bring it back. Ask the desktop app
(has_tray_host); without a tray, say so, focus "Quit when I close the
window", and offer "Keep running in the background" as the secondary
choice. Older desktop builds keep the current dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
2026-10-18 02:41:04 -04:00

161 lines
5.7 KiB
TypeScript

import React, { useCallback, useEffect, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import {
Box,
Button,
Checkbox,
Dialog,
Header,
Overlay,
OverlayBackdrop,
OverlayCenter,
Text,
config,
} from 'folds';
import { invokeTauri, isTauri, tauriInvoke, useTauriEvent } from '../hooks/useTauri';
import { useModalStyle } from '../hooks/useModalStyle';
/**
* cinny-desktop #5: the first time the window is closed, ask whether Lotus Chat
* should keep running in the tray (calls, messages, notifications) or quit —
* and, in the same moment, whether it should start when you sign in. Asked
* once; changeable in Settings → General. The native side sends
* `lotus-close-requested` only while the choice is still "ask", and never
* during a call (a call always goes to the tray).
*/
export function CloseBehaviorPrompt() {
const [open, setOpen] = useState(false);
const [launchOnLogin, setLaunchOnLogin] = useState(false);
// [cinny-desktop #37] Stock GNOME has no tray host: "keep running in the
// tray" would hide the window with no icon to bring it back. Older desktop
// builds lack the command; assume a tray then (the old behaviour).
const [hasTray, setHasTray] = useState(true);
const modalStyle = useModalStyle(420);
// Tells the native side we're listening (see get_close_behavior).
useEffect(() => {
if (!isTauri()) return;
tauriInvoke()?.('get_close_behavior').catch(() => undefined);
}, []);
useTauriEvent('lotus-close-requested', () => {
const ask = tauriInvoke()?.('has_tray_host');
if (!ask) {
setOpen(true);
return;
}
ask
.then((v) => setHasTray(v !== false))
.catch(() => setHasTray(true))
.finally(() => setOpen(true));
});
const choose = useCallback(
(value: 'tray' | 'quit') => {
if (launchOnLogin) invokeTauri('plugin:autostart|enable');
setOpen(false);
invokeTauri('resolve_close_request', { value });
},
[launchOnLogin],
);
if (!open) return null;
return (
<Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter>
<FocusTrap
focusTrapOptions={{
initialFocus: hasTray ? '#close-behavior-keep' : '#close-behavior-quit',
// Dismissing without choosing keeps the window open; the next close
// asks again.
onDeactivate: () => setOpen(false),
clickOutsideDeactivates: true,
}}
>
<Dialog
variant="Surface"
role="dialog"
aria-modal="true"
aria-labelledby="close-behavior-title"
aria-describedby="close-behavior-body"
style={modalStyle}
>
<Header
style={{
padding: `0 ${config.space.S400}`,
borderBottomWidth: config.borderWidth.B300,
}}
variant="Surface"
size="500"
>
<Text as="h2" size="H4" id="close-behavior-title">
Keep Lotus Chat running?
</Text>
</Header>
<Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
<Text priority="400" id="close-behavior-body">
{hasTray
? 'Lotus Chat can keep running in the system tray when you close this window, so messages, notifications and calls still reach you. Open it again from the tray icon.'
: "Your desktop doesn't show tray icons, so if Lotus Chat keeps running after you close this window, the only way back is to launch it again."}
</Text>
<Box as="label" alignItems="Center" gap="200" style={{ cursor: 'pointer' }}>
<Checkbox
size="300"
variant="Primary"
checked={launchOnLogin}
onClick={(e: React.MouseEvent<HTMLInputElement>) =>
setLaunchOnLogin(e.currentTarget.checked)
}
/>
<Text size="T300">Start Lotus Chat when I sign in to my computer</Text>
</Box>
<Box direction="Column" gap="200">
{hasTray ? (
<>
<Button
id="close-behavior-keep"
variant="Primary"
onClick={() => choose('tray')}
>
<Text size="B400">Keep running in the tray</Text>
</Button>
<Button
id="close-behavior-quit"
variant="Secondary"
fill="Soft"
onClick={() => choose('quit')}
>
<Text size="B400">Quit when I close the window</Text>
</Button>
</>
) : (
<>
<Button
id="close-behavior-quit"
variant="Primary"
onClick={() => choose('quit')}
>
<Text size="B400">Quit when I close the window</Text>
</Button>
<Button
id="close-behavior-keep"
variant="Secondary"
fill="Soft"
onClick={() => choose('tray')}
>
<Text size="B400">Keep running in the background</Text>
</Button>
</>
)}
</Box>
<Text size="T200" priority="300">
You can change this in Settings → General.
</Text>
</Box>
</Dialog>
</FocusTrap>
</OverlayCenter>
</Overlay>
);
}