= ({
{todos.map((todo) => {
const isExpandedTodo = expandedTodoIds.has(todo.id);
return (
-
- handleToggleTodo(todo.id, checked)}
- ariaLabel={`Mark "${todo.text}" complete`}
- className="mt-[3px] self-start"
- />
-
-
+
+
+ handleToggleTodo(todo.id, checked)}
+ ariaLabel={`Mark "${todo.text}" complete`}
+ />
+
-
-
-
-
-
- handleSendToCurrentSession(todo.text)}>
- Send to current session
-
- handleSendToNewSession(todo.id, todo.text)}>
- Send to new session
-
- void handleSendToNewWorktreeSession(todo.id, todo.text)}
- disabled={!canCreateWorktree}
- >
- Send to new worktree session
-
-
-
-
-
+
+
+
+
+
+
+
+ handleSendToCurrentSession(todo.text)}>
+ Send to current session
+
+ handleSendToNewSession(todo.id, todo.text)}>
+ Send to new session
+
+ void handleSendToNewWorktreeSession(todo.id, todo.text)}
+ disabled={!canCreateWorktree}
+ >
+ Send to new worktree session
+
+
+
+
+
);
})}
diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx
index dc8deafd..6a28a67e 100644
--- a/packages/ui/src/components/session/SessionSidebar.tsx
+++ b/packages/ui/src/components/session/SessionSidebar.tsx
@@ -3,7 +3,8 @@ import type { Session } from '@opencode-ai/sdk/v2';
import { RiLayoutLeftLine } from '@remixicon/react';
import { toast } from '@/components/ui';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
-import { isDesktopLocalOriginActive, isDesktopShell, isTauriShell, startDesktopWindowDrag } from '@/lib/desktop';
+import { isDesktopLocalOriginActive, isDesktopShell, isTauriShell } from '@/lib/desktop';
+import { isDesktopWindowFullscreen as getDesktopWindowFullscreen, onDesktopWindowResized, startDesktopWindowDrag } from '@/lib/desktopNative';
import { sessionEvents } from '@/lib/sessionEvents';
import { formatDirectoryName, cn } from '@/lib/utils';
import { useSessionUIStore } from '@/sync/session-ui-store';
@@ -403,9 +404,7 @@ export const SessionSidebar: React.FC = ({
const syncFullscreenState = async () => {
try {
- const { getCurrentWindow } = await import('@tauri-apps/api/window');
- const currentWindow = getCurrentWindow();
- const fullscreen = await currentWindow.isFullscreen();
+ const fullscreen = await getDesktopWindowFullscreen();
if (!disposed) {
setIsDesktopWindowFullscreen(fullscreen);
}
@@ -418,9 +417,7 @@ export const SessionSidebar: React.FC = ({
const attach = async () => {
try {
- const { getCurrentWindow } = await import('@tauri-apps/api/window');
- const currentWindow = getCurrentWindow();
- unlistenResize = await currentWindow.onResized(() => {
+ unlistenResize = onDesktopWindowResized(() => {
void syncFullscreenState();
});
} catch {
diff --git a/packages/ui/src/components/views/FilesView.tsx b/packages/ui/src/components/views/FilesView.tsx
index a35741c0..7ab196e2 100644
--- a/packages/ui/src/components/views/FilesView.tsx
+++ b/packages/ui/src/components/views/FilesView.tsx
@@ -80,8 +80,7 @@ import { useEffectiveDirectory } from '@/hooks/useEffectiveDirectory';
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
import { ensurePierreThemeRegistered } from '@/lib/shiki/appThemeRegistry';
import { getDefaultTheme } from '@/lib/theme/themes';
-import { openDesktopPath, openDesktopProjectInApp } from '@/lib/desktop';
-import { OPEN_DIRECTORY_APP_IDS } from '@/lib/openInApps';
+import { openDesktopFileInApp, openDesktopPath } from '@/lib/desktop';
import { useOpenInAppsStore } from '@/stores/useOpenInAppsStore';
import { eventMatchesShortcut, getEffectiveShortcutCombo } from '@/lib/shortcuts';
@@ -510,17 +509,32 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
const [isFloatingToolbarOpen, setIsFloatingToolbarOpen] = React.useState(false);
const floatingToolbarRef = React.useRef(null);
+ const toolbarDropdownOpenCountRef = React.useRef(0);
+
+ const handleToolbarDropdownOpenChange = React.useCallback((open: boolean) => {
+ toolbarDropdownOpenCountRef.current = Math.max(
+ 0,
+ toolbarDropdownOpenCountRef.current + (open ? 1 : -1),
+ );
+ }, []);
+
+ const isClickInsidePortalledMenu = React.useCallback((target: EventTarget | null) => {
+ if (!(target instanceof Element)) return false;
+ return target.closest('[data-slot="dropdown-menu-content"], [data-slot="dropdown-menu-item"]') !== null;
+ }, []);
React.useEffect(() => {
if (!isFloatingToolbarOpen) return;
const handler = (event: MouseEvent) => {
+ if (toolbarDropdownOpenCountRef.current > 0) return;
+ if (isClickInsidePortalledMenu(event.target)) return;
if (floatingToolbarRef.current && !floatingToolbarRef.current.contains(event.target as Node)) {
setIsFloatingToolbarOpen(false);
}
};
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
- }, [isFloatingToolbarOpen]);
+ }, [isClickInsidePortalledMenu, isFloatingToolbarOpen]);
const [textViewMode, setTextViewMode] = React.useState<'view' | 'edit'>('edit');
const [mdViewMode, setMdViewMode] = React.useState<'preview' | 'edit'>('edit');
const [jsonViewMode, setJsonViewMode] = React.useState<'tree' | 'text'>('tree');
@@ -664,21 +678,11 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
}, [files]);
const handleOpenInApp = React.useCallback(async (app: { id: string; appName: string }) => {
- if (!selectedFile?.path || !root) {
+ if (!selectedFile?.path) {
return;
}
- const fileDirectory = getParentDirectoryPath(selectedFile.path) || root;
-
- if (OPEN_DIRECTORY_APP_IDS.has(app.id)) {
- const openedDirectory = await openDesktopPath(fileDirectory, app.appName);
- if (!openedDirectory) {
- toast.error(`Failed to open in ${app.appName}`);
- }
- return;
- }
-
- const openedInApp = await openDesktopProjectInApp(root, app.id, app.appName, selectedFile.path);
+ const openedInApp = await openDesktopFileInApp(selectedFile.path, app.id, app.appName);
if (openedInApp) {
return;
}
@@ -688,10 +692,14 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
return;
}
- const openedDirectory = await openDesktopPath(fileDirectory, app.appName);
- if (!openedDirectory) {
- toast.error(`Failed to open in ${app.appName}`);
+ const fileDirectory = getParentDirectoryPath(selectedFile.path) || root;
+ if (fileDirectory) {
+ const openedDirectory = await openDesktopPath(fileDirectory, app.appName);
+ if (openedDirectory) {
+ return;
+ }
}
+ toast.error(`Failed to open in ${app.appName}`);
}, [root, selectedFile?.path]);
const handleOpenDialog = React.useCallback((type: 'createFile' | 'createFolder' | 'rename' | 'delete', data: { path: string; name?: string; type?: 'file' | 'directory' }) => {
@@ -2354,7 +2362,7 @@ export const FilesView: React.FC = ({ mode = 'full' }) => {
) : null
)}
-
+