chore: finalize electron desktop migration

This commit is contained in:
Mohamed Boudra
2026-03-21 01:50:00 +07:00
parent 77fb74c188
commit 111576e2ca
169 changed files with 6463 additions and 13191 deletions
@@ -1,21 +1,21 @@
import { describe, expect, it, vi, beforeEach } from "vitest";
const tauriState = vi.hoisted(() => ({
const desktopHostState = vi.hoisted(() => ({
api: null as any,
}));
vi.mock("@/utils/tauri", () => ({
getTauri: () => tauriState.api,
vi.mock("@/desktop/host", () => ({
getDesktopHost: () => desktopHostState.api,
}));
import {
normalizePickedImageAssets,
openImagePathsWithTauriDialog,
openImagePathsWithDesktopDialog,
} from "./image-attachment-picker";
describe("image-attachment-picker", () => {
beforeEach(() => {
tauriState.api = null;
desktopHostState.api = null;
});
it("normalizes a picked File into a blob source", async () => {
@@ -69,13 +69,13 @@ describe("image-attachment-picker", () => {
expect(result[0]?.mimeType).toBe("image/png");
});
it("uses the tauri dialog api when available", async () => {
it("uses the desktop dialog api when available", async () => {
const open = vi.fn().mockResolvedValue(["/tmp/one.png", "/tmp/two.jpg"]);
tauriState.api = {
desktopHostState.api = {
dialog: { open },
};
const result = await openImagePathsWithTauriDialog();
const result = await openImagePathsWithDesktopDialog();
expect(open).toHaveBeenCalledWith(
expect.objectContaining({
@@ -87,21 +87,11 @@ describe("image-attachment-picker", () => {
expect(result).toEqual(["/tmp/one.png", "/tmp/two.jpg"]);
});
it("falls back to core invoke for the tauri dialog plugin", async () => {
const invoke = vi.fn().mockResolvedValue("/tmp/one.png");
tauriState.api = {
core: { invoke },
};
it("throws when desktop dialog API is not available", async () => {
desktopHostState.api = {};
const result = await openImagePathsWithTauriDialog();
expect(invoke).toHaveBeenCalledWith("plugin:dialog|open", {
options: expect.objectContaining({
multiple: true,
directory: false,
title: "Attach images",
}),
});
expect(result).toEqual(["/tmp/one.png"]);
await expect(openImagePathsWithDesktopDialog()).rejects.toThrow(
"Desktop dialog API is not available."
);
});
});
@@ -1,4 +1,4 @@
import { getTauri } from "@/utils/tauri";
import { getDesktopHost } from "@/desktop/host";
export type PickedImageSource =
| { kind: "file_uri"; uri: string }
@@ -77,15 +77,15 @@ export async function normalizePickedImageAssets(
);
}
function normalizeTauriDialogSelection(selection: string | string[] | null): string[] {
function normalizeDesktopDialogSelection(selection: string | string[] | null): string[] {
if (!selection) {
return [];
}
return Array.isArray(selection) ? selection : [selection];
}
export async function openImagePathsWithTauriDialog(): Promise<string[]> {
const tauri = getTauri();
export async function openImagePathsWithDesktopDialog(): Promise<string[]> {
const desktop = getDesktopHost();
const options = {
directory: false,
multiple: true,
@@ -93,18 +93,10 @@ export async function openImagePathsWithTauriDialog(): Promise<string[]> {
title: "Attach images",
};
const dialogOpen = tauri?.dialog?.open;
if (typeof dialogOpen === "function") {
return normalizeTauriDialogSelection(await dialogOpen(options));
const dialogOpen = desktop?.dialog?.open;
if (typeof dialogOpen !== "function") {
throw new Error("Desktop dialog API is not available.");
}
const invoke = tauri?.core?.invoke;
if (typeof invoke !== "function") {
throw new Error("Tauri dialog API is not available.");
}
const result = await invoke("plugin:dialog|open", { options });
return normalizeTauriDialogSelection(
Array.isArray(result) || typeof result === "string" || result === null ? result : null
);
return normalizeDesktopDialogSelection(await dialogOpen(options));
}
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { AttemptCancelledError, AttemptGuard } from "@/utils/attempt-guard";
import { getTauri } from "@/utils/tauri";
import { isDesktop } from "@/desktop/host";
export interface AudioCaptureConfig {
sampleRate?: number;
@@ -157,20 +157,20 @@ export function useAudioRecorder(config?: AudioCaptureConfig) {
: true;
const currentOrigin =
typeof window !== "undefined" && window.location ? window.location.origin : "unknown";
const isTauri = getTauri() !== null;
const isDesktopApp = isDesktop();
if (missingNavigator) {
throw new Error("Microphone capture is not supported in this environment");
}
if (!secureContext && !isTauri) {
if (!secureContext && !isDesktopApp) {
throw new Error(
`Microphone access requires HTTPS or localhost. Current origin: ${currentOrigin}`
);
}
if (!secureContext && isTauri) {
if (!secureContext && isDesktopApp) {
console.warn(
"[AudioRecorder][Web] Insecure context reported under Tauri; attempting getUserMedia anyway",
"[AudioRecorder][Web] Insecure context reported under Desktop; attempting getUserMedia anyway",
{ currentOrigin }
);
}
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { parsePcm16Wav } from "@/utils/pcm16-wav";
import { getTauri } from "@/utils/tauri";
import { isDesktop } from "@/desktop/host";
import type { DictationAudioSource, DictationAudioSourceConfig } from "./use-dictation-audio-source.types";
@@ -158,17 +158,17 @@ export function useDictationAudioSource(config: DictationAudioSourceConfig): Dic
: true;
const currentOrigin =
typeof window !== "undefined" && window.location ? window.location.origin : "unknown";
const isTauri = getTauri() !== null;
const isDesktopApp = isDesktop();
if (missingNavigator) {
throw new Error("Microphone capture is not supported in this environment");
}
if (!secureContext && !isTauri) {
if (!secureContext && !isDesktopApp) {
throw new Error(`Microphone access requires HTTPS or localhost. Current origin: ${currentOrigin}`);
}
if (!secureContext && isTauri) {
if (!secureContext && isDesktopApp) {
console.warn(
"[DictationAudio][Web] Insecure context reported under Tauri; attempting getUserMedia anyway",
"[DictationAudio][Web] Insecure context reported under Desktop; attempting getUserMedia anyway",
{ currentOrigin }
);
}
+6 -6
View File
@@ -1,8 +1,8 @@
import { useEffect, useRef, useState } from "react";
import { Platform } from "react-native";
import { getIsTauriMac } from "@/constants/layout";
import { getIsDesktopMac } from "@/constants/layout";
import { useAggregatedAgents } from "./use-aggregated-agents";
import { getCurrentTauriWindow } from "@/utils/tauri";
import { getDesktopHost } from "@/desktop/host";
type FaviconStatus = "none" | "running" | "attention";
type ColorScheme = "dark" | "light";
@@ -92,15 +92,15 @@ function getSystemColorScheme(): ColorScheme {
}
async function updateMacDockBadge(count?: number) {
if (Platform.OS !== "web" || !getIsTauriMac()) return;
if (Platform.OS !== "web" || !getIsDesktopMac()) return;
const tauriWindow = getCurrentTauriWindow();
if (!tauriWindow || typeof tauriWindow.setBadgeCount !== "function") {
const desktopWindow = getDesktopHost()?.window?.getCurrentWindow?.();
if (!desktopWindow || typeof desktopWindow.setBadgeCount !== "function") {
return;
}
try {
await tauriWindow.setBadgeCount(count);
await desktopWindow.setBadgeCount(count);
} catch (error) {
console.warn("[useFaviconStatus] Failed to update macOS dock badge", error);
}
+16 -15
View File
@@ -1,7 +1,7 @@
import { useState, useRef, useEffect } from "react";
import { Platform } from "react-native";
import type { ImageAttachment } from "@/components/message-input";
import { getCurrentTauriWindow, getTauri } from "@/utils/tauri";
import { getDesktopHost } from "@/desktop/host";
import {
persistAttachmentFromBlob,
persistAttachmentFromFileUri,
@@ -33,7 +33,7 @@ const IMAGE_MIME_BY_EXTENSION: Record<string, string> = {
".tiff": "image/tiff",
};
type TauriDragDropPayload =
type DesktopDragDropPayload =
| {
type: "enter";
paths: string[];
@@ -49,8 +49,8 @@ type TauriDragDropPayload =
type: "leave";
};
type TauriDragDropEvent = {
payload: TauriDragDropPayload;
type DesktopDragDropEvent = {
payload: DesktopDragDropPayload;
};
function isImageFile(file: File): boolean {
@@ -121,26 +121,27 @@ export function useFileDropZone({
didCleanup = true;
try {
void Promise.resolve(cleanupFn()).catch((error) => {
console.warn("[useFileDropZone] Failed to remove Tauri drag-drop listener:", error);
console.warn("[useFileDropZone] Failed to remove desktop drag-drop listener:", error);
});
} catch (error) {
console.warn("[useFileDropZone] Failed to remove Tauri drag-drop listener:", error);
console.warn("[useFileDropZone] Failed to remove desktop drag-drop listener:", error);
}
}
async function setupTauriDragDrop(): Promise<boolean> {
if (getTauri() === null) {
async function setupDesktopDragDrop(): Promise<boolean> {
const desktopHost = getDesktopHost();
if (desktopHost === null) {
return false;
}
const tauriWindow = getCurrentTauriWindow();
if (!tauriWindow || typeof tauriWindow.onDragDropEvent !== "function") {
const desktopWindow = desktopHost.window?.getCurrentWindow?.();
if (!desktopWindow || typeof desktopWindow.onDragDropEvent !== "function") {
return false;
}
try {
const unlisten = await tauriWindow.onDragDropEvent(
(event: TauriDragDropEvent) => {
const unlisten = await desktopWindow.onDragDropEvent(
(event: DesktopDragDropEvent) => {
const payload = event.payload;
if (payload.type === "leave") {
setIsDragging(false);
@@ -185,7 +186,7 @@ export function useFileDropZone({
cleanup = unlisten;
return true;
} catch (error) {
console.warn("[useFileDropZone] Failed to listen for Tauri drag-drop:", error);
console.warn("[useFileDropZone] Failed to listen for desktop drag-drop:", error);
return false;
}
}
@@ -269,8 +270,8 @@ export function useFileDropZone({
}
void (async () => {
const tauriListenersAttached = await setupTauriDragDrop();
if (disposed || tauriListenersAttached) {
const desktopListenersAttached = await setupDesktopDragDrop();
if (disposed || desktopListenersAttached) {
return;
}
setupDomDragDrop();
@@ -2,10 +2,10 @@ import { useCallback, useRef } from "react";
import { Alert } from "react-native";
import { Platform } from "react-native";
import * as ImagePicker from "expo-image-picker";
import { isTauriEnvironment } from "@/utils/tauri";
import { isDesktop } from "@/desktop/host";
import {
normalizePickedImageAssets,
openImagePathsWithTauriDialog,
openImagePathsWithDesktopDialog,
type PickedImageAttachmentInput,
} from "@/hooks/image-attachment-picker";
@@ -42,8 +42,8 @@ export function useImageAttachmentPicker(): UseImageAttachmentPickerResult {
isPickingRef.current = true;
try {
if (Platform.OS === "web" && isTauriEnvironment()) {
const selectedPaths = await openImagePathsWithTauriDialog();
if (Platform.OS === "web" && isDesktop()) {
const selectedPaths = await openImagePathsWithDesktopDialog();
if (selectedPaths.length === 0) {
return null;
}
@@ -1,15 +1,14 @@
import { useQuery } from '@tanstack/react-query'
import { getManagedDaemonStatus } from '@/desktop/managed-runtime/managed-runtime'
import { isTauriEnvironment } from '@/utils/tauri'
import { getDesktopDaemonStatus, shouldUseDesktopDaemon } from '@/desktop/daemon/desktop-daemon'
const MANAGED_DAEMON_SERVER_ID_QUERY_KEY = ['managed-daemon-server-id'] as const
const DESKTOP_DAEMON_SERVER_ID_QUERY_KEY = ['desktop-daemon-server-id'] as const
interface ManagedDaemonServerIdResult {
interface DesktopDaemonServerIdResult {
serverId: string | null
}
async function loadManagedDaemonServerId(): Promise<ManagedDaemonServerIdResult> {
const status = await getManagedDaemonStatus()
async function loadDesktopDaemonServerId(): Promise<DesktopDaemonServerIdResult> {
const status = await getDesktopDaemonStatus()
const serverId = status.serverId.trim()
return {
serverId: serverId.length > 0 ? serverId : null,
@@ -18,14 +17,15 @@ async function loadManagedDaemonServerId(): Promise<ManagedDaemonServerIdResult>
export function useIsLocalDaemon(serverId: string): boolean {
const normalizedServerId = serverId.trim()
const isDesktop = isTauriEnvironment()
const isDesktop = shouldUseDesktopDaemon()
const query = useQuery({
queryKey: MANAGED_DAEMON_SERVER_ID_QUERY_KEY,
queryFn: loadManagedDaemonServerId,
queryKey: DESKTOP_DAEMON_SERVER_ID_QUERY_KEY,
queryFn: loadDesktopDaemonServerId,
enabled: isDesktop,
staleTime: Infinity,
gcTime: Infinity,
refetchInterval: (query) => (query.state.data?.serverId ? false : 1000),
refetchOnMount: false,
refetchOnReconnect: false,
refetchOnWindowFocus: false,
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { Platform } from "react-native";
import { usePathname } from "expo-router";
import { getIsTauri } from "@/constants/layout";
import { getIsDesktop } from "@/constants/layout";
import { useHosts } from "@/runtime/host-runtime";
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
@@ -50,7 +50,7 @@ export function useKeyboardShortcuts({
if (Platform.OS !== "web") return;
if (isMobile) return;
const isTauri = getIsTauri();
const isDesktopApp = getIsDesktop();
const isMac = getShortcutOs() === "mac";
const shouldHandle = () => {
@@ -259,7 +259,7 @@ export function useKeyboardShortcuts({
if (key === "Alt" && !event.shiftKey) {
useKeyboardShortcutsStore.getState().setAltDown(true);
}
if (isTauri && (key === "Meta" || key === "Control") && !event.shiftKey) {
if (isDesktopApp && (key === "Meta" || key === "Control") && !event.shiftKey) {
useKeyboardShortcutsStore.getState().setCmdOrCtrlDown(true);
}
if (key === "Shift") {
@@ -278,7 +278,7 @@ export function useKeyboardShortcuts({
event,
context: {
isMac,
isTauri,
isDesktop: isDesktopApp,
focusScope,
commandCenterOpen: store.commandCenterOpen,
hasSelectedAgent: canToggleFileExplorerShortcut({
@@ -314,7 +314,7 @@ export function useKeyboardShortcuts({
if (key === "Alt") {
useKeyboardShortcutsStore.getState().setAltDown(false);
}
if (isTauri && (key === "Meta" || key === "Control")) {
if (isDesktopApp && (key === "Meta" || key === "Control")) {
useKeyboardShortcutsStore.getState().setCmdOrCtrlDown(false);
}
};