diff --git a/apps/code/src/main/preload.ts b/apps/code/src/main/preload.ts index 88f4dfa96..51b354da7 100644 --- a/apps/code/src/main/preload.ts +++ b/apps/code/src/main/preload.ts @@ -1,6 +1,11 @@ import { exposeElectronTRPC } from "@posthog/electron-trpc/main"; +import { contextBridge, webUtils } from "electron"; import "electron-log/preload"; +contextBridge.exposeInMainWorld("electronUtils", { + getPathForFile: (file: File) => webUtils.getPathForFile(file), +}); + process.once("loaded", async () => { exposeElectronTRPC(); }); diff --git a/apps/code/src/renderer/features/message-editor/components/AttachmentMenu.tsx b/apps/code/src/renderer/features/message-editor/components/AttachmentMenu.tsx index 73704b692..d9b4454cc 100644 --- a/apps/code/src/renderer/features/message-editor/components/AttachmentMenu.tsx +++ b/apps/code/src/renderer/features/message-editor/components/AttachmentMenu.tsx @@ -4,6 +4,7 @@ import { IconButton, Popover } from "@radix-ui/themes"; import { trpcClient, useTRPC } from "@renderer/trpc/client"; import { toast } from "@renderer/utils/toast"; import { useQuery } from "@tanstack/react-query"; +import { getFilePath } from "@utils/getFilePath"; import { getFileName } from "@utils/path"; import { useRef, useState } from "react"; import type { FileAttachment, MentionChip } from "../utils/content"; @@ -69,7 +70,7 @@ export function AttachmentMenu({ try { const attachments = await Promise.all( files.map(async (file) => { - const filePath = (file as globalThis.File & { path?: string }).path; + const filePath = getFilePath(file); if (filePath) { return { id: filePath, label: file.name } satisfies FileAttachment; } diff --git a/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts b/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts index a5752157a..db7a8f895 100644 --- a/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts +++ b/apps/code/src/renderer/features/message-editor/tiptap/useTiptapEditor.ts @@ -4,6 +4,7 @@ import { toast } from "@renderer/utils/toast"; import { useSettingsStore } from "@stores/settingsStore"; import type { EditorView } from "@tiptap/pm/view"; import { useEditor } from "@tiptap/react"; +import { getFilePath } from "@utils/getFilePath"; import type React from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { usePromptHistoryStore } from "../stores/promptHistoryStore"; @@ -270,8 +271,7 @@ export function useTiptapEditor(options: UseTiptapEditorOptions) { const newAttachments: FileAttachment[] = []; for (let i = 0; i < files.length; i++) { const file = files[i]; - // In Electron, File objects have a 'path' property - const path = (file as unknown as { path?: string }).path; + const path = getFilePath(file); if (path) { newAttachments.push({ id: path, label: file.name }); } diff --git a/apps/code/src/renderer/features/sessions/components/SessionView.tsx b/apps/code/src/renderer/features/sessions/components/SessionView.tsx index 5ce4716fc..b18f24631 100644 --- a/apps/code/src/renderer/features/sessions/components/SessionView.tsx +++ b/apps/code/src/renderer/features/sessions/components/SessionView.tsx @@ -20,6 +20,7 @@ import { isJsonRpcNotification, isJsonRpcResponse, } from "@shared/types/session-events"; +import { getFilePath } from "@utils/getFilePath"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { getSessionService } from "../service/service"; @@ -328,7 +329,7 @@ export function SessionView({ for (let i = 0; i < files.length; i++) { const file = files[i]; - const filePath = (file as File & { path?: string }).path; + const filePath = getFilePath(file); if (filePath) { editorRef.current?.addAttachment({ id: filePath, diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx index 10bacc8af..2f84abfb4 100644 --- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx +++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx @@ -29,6 +29,7 @@ import { useAuthStore } from "@renderer/features/auth/stores/authStore"; import { useTRPC } from "@renderer/trpc/client"; import { useNavigationStore } from "@stores/navigationStore"; import { useQuery } from "@tanstack/react-query"; +import { getFilePath } from "@utils/getFilePath"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useHotkeys } from "react-hotkeys-hook"; import { usePreviewConfig } from "../hooks/usePreviewConfig"; @@ -344,7 +345,7 @@ export function TaskInput({ for (let i = 0; i < files.length; i++) { const file = files[i]; - const filePath = (file as File & { path?: string }).path; + const filePath = getFilePath(file); if (filePath) { editorRef.current?.addAttachment({ id: filePath, diff --git a/apps/code/src/renderer/types/electron.d.ts b/apps/code/src/renderer/types/electron.d.ts index 322a3631c..9e0418bc3 100644 --- a/apps/code/src/renderer/types/electron.d.ts +++ b/apps/code/src/renderer/types/electron.d.ts @@ -1,3 +1,11 @@ import "@main/services/types"; // No legacy IPC interfaces - all communication now uses tRPC + +declare global { + interface Window { + electronUtils?: { + getPathForFile: (file: File) => string; + }; + } +} diff --git a/apps/code/src/renderer/utils/getFilePath.ts b/apps/code/src/renderer/utils/getFilePath.ts new file mode 100644 index 000000000..63ae65636 --- /dev/null +++ b/apps/code/src/renderer/utils/getFilePath.ts @@ -0,0 +1,13 @@ +/** + * Get the filesystem path for a File from a drag-and-drop or file input event. + * + * In Electron 32+ with contextIsolation, File.path is empty. The preload + * script exposes webUtils.getPathForFile as window.electronUtils.getPathForFile + * to bridge this gap. + */ +export function getFilePath(file: File): string { + if (window.electronUtils?.getPathForFile) { + return window.electronUtils.getPathForFile(file); + } + return (file as File & { path?: string }).path ?? ""; +}