From 724c9af43c253f2a71a3dacbc71f463e9101d14d Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sat, 28 Mar 2026 17:38:10 +0000 Subject: [PATCH 1/5] Show parent directory in file mention chips --- .../suggestions/getSuggestions.ts | 19 ++++++++++++------- .../message-editor/tiptap/MentionChipView.tsx | 9 ++++++++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts index bbaed3a55..8767221ed 100644 --- a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts +++ b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts @@ -52,13 +52,18 @@ export async function getFileSuggestions( const { files, fzf } = await fetchRepoFiles(repoPath); const matched = searchFiles(fzf, files, query); - return matched.map((file) => ({ - id: file.path, - label: file.name, - description: file.dir || undefined, - filename: file.name, - path: file.path, - })); + return matched.map((file) => { + const parentDir = file.dir ? file.dir.split("/").pop() : undefined; + const label = parentDir ? `${parentDir}/${file.name}` : file.name; + + return { + id: file.path, + label, + description: file.dir || undefined, + filename: file.name, + path: file.path, + }; + }); } export function getCommandSuggestions( diff --git a/apps/code/src/renderer/features/message-editor/tiptap/MentionChipView.tsx b/apps/code/src/renderer/features/message-editor/tiptap/MentionChipView.tsx index 0a459455d..ae1733913 100644 --- a/apps/code/src/renderer/features/message-editor/tiptap/MentionChipView.tsx +++ b/apps/code/src/renderer/features/message-editor/tiptap/MentionChipView.tsx @@ -35,8 +35,9 @@ function DefaultChip({ const isCommand = type === "command"; const prefix = isCommand ? "/" : "@"; + const isFile = type === "file"; - return ( + const chip = ( ); + + if (isFile) { + return {chip}; + } + + return chip; } function PastedTextChip({ From d984bc23c5b1468d36018188fa04f87c2f138b1a Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Sun, 29 Mar 2026 10:21:01 +0100 Subject: [PATCH 2/5] Chipify absolute file paths in @ mentions --- apps/code/src/main/services/fs/schemas.ts | 1 + apps/code/src/main/services/fs/service.ts | 9 ++++ apps/code/src/main/trpc/routers/fs.ts | 6 +++ .../suggestions/getSuggestions.ts | 43 +++++++++++++++++-- 4 files changed, 56 insertions(+), 3 deletions(-) diff --git a/apps/code/src/main/services/fs/schemas.ts b/apps/code/src/main/services/fs/schemas.ts index fec9d26df..2981517b4 100644 --- a/apps/code/src/main/services/fs/schemas.ts +++ b/apps/code/src/main/services/fs/schemas.ts @@ -29,6 +29,7 @@ const fileEntry = z.object({ export const listRepoFilesOutput = z.array(fileEntry); export const readRepoFileOutput = z.string().nullable(); +export const fileExistsOutput = z.boolean(); export type ListRepoFilesInput = z.infer; export type ReadRepoFileInput = z.infer; diff --git a/apps/code/src/main/services/fs/service.ts b/apps/code/src/main/services/fs/service.ts index 2e00f0d91..52e0844b8 100644 --- a/apps/code/src/main/services/fs/service.ts +++ b/apps/code/src/main/services/fs/service.ts @@ -97,6 +97,15 @@ export class FsService { } } + async fileExists(filePath: string): Promise { + try { + const stat = await fs.promises.stat(path.resolve(filePath)); + return stat.isFile(); + } catch { + return false; + } + } + async readAbsoluteFile(filePath: string): Promise { try { return await fs.promises.readFile(path.resolve(filePath), "utf-8"); diff --git a/apps/code/src/main/trpc/routers/fs.ts b/apps/code/src/main/trpc/routers/fs.ts index 1aede0b35..81379c81b 100644 --- a/apps/code/src/main/trpc/routers/fs.ts +++ b/apps/code/src/main/trpc/routers/fs.ts @@ -1,6 +1,7 @@ import { container } from "../../di/container"; import { MAIN_TOKENS } from "../../di/tokens"; import { + fileExistsOutput, listRepoFilesInput, listRepoFilesOutput, readAbsoluteFileInput, @@ -33,6 +34,11 @@ export const fsRouter = router({ .output(readRepoFileOutput) .query(({ input }) => getService().readAbsoluteFile(input.filePath)), + fileExists: publicProcedure + .input(readAbsoluteFileInput) + .output(fileExistsOutput) + .query(({ input }) => getService().fileExists(input.filePath)), + readFileAsBase64: publicProcedure .input(readAbsoluteFileInput) .output(readRepoFileOutput) diff --git a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts index 8767221ed..80bf1b3c9 100644 --- a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts +++ b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts @@ -1,7 +1,13 @@ import type { AvailableCommand } from "@agentclientprotocol/sdk"; import { CODE_COMMANDS } from "@features/message-editor/commands"; import { getAvailableCommandsForTask } from "@features/sessions/stores/sessionStore"; -import { fetchRepoFiles, searchFiles } from "@hooks/useRepoFiles"; +import { + fetchRepoFiles, + pathToFileItem, + searchFiles, +} from "@hooks/useRepoFiles"; +import { trpcClient } from "@renderer/trpc/client"; +import { isAbsolutePath } from "@utils/path"; import Fuse, { type IFuseOptions } from "fuse.js"; import { useDraftStore } from "../stores/draftStore"; import type { CommandSuggestionItem, FileSuggestionItem } from "../types"; @@ -39,20 +45,44 @@ function searchCommands( return results.map((result) => result.item); } +async function getAbsolutePathSuggestion( + query: string, +): Promise { + if (!isAbsolutePath(query)) return null; + + try { + const exists = await trpcClient.fs.fileExists.query({ filePath: query }); + if (!exists) return null; + } catch { + return null; + } + + const fileItem = pathToFileItem(query); + return { + id: query, + label: fileItem.name, + description: fileItem.dir || undefined, + filename: fileItem.name, + path: query, + }; +} + export async function getFileSuggestions( sessionId: string, query: string, ): Promise { const repoPath = useDraftStore.getState().contexts[sessionId]?.repoPath; + const absolutePathSuggestion = getAbsolutePathSuggestion(query); if (!repoPath) { - return []; + const resolved = await absolutePathSuggestion; + return resolved ? [resolved] : []; } const { files, fzf } = await fetchRepoFiles(repoPath); const matched = searchFiles(fzf, files, query); - return matched.map((file) => { + const results: FileSuggestionItem[] = matched.map((file) => { const parentDir = file.dir ? file.dir.split("/").pop() : undefined; const label = parentDir ? `${parentDir}/${file.name}` : file.name; @@ -64,6 +94,13 @@ export async function getFileSuggestions( path: file.path, }; }); + + const resolved = await absolutePathSuggestion; + if (resolved && !results.some((r) => r.id === resolved.id)) { + results.unshift(resolved); + } + + return results; } export function getCommandSuggestions( From 768a86f5df6fdc55f0eefd83baf1c309f684b97f Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Thu, 2 Apr 2026 15:03:29 -0700 Subject: [PATCH 3/5] Update getSuggestions.ts --- .../suggestions/getSuggestions.ts | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts index 80bf1b3c9..68650b554 100644 --- a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts +++ b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts @@ -45,10 +45,16 @@ function searchCommands( return results.map((result) => result.item); } +function parentDirLabel(dir: string, name: string): string { + const parent = dir.split("/").filter(Boolean).pop(); + return parent ? `${parent}/${name}` : name; +} + async function getAbsolutePathSuggestion( query: string, ): Promise { if (!isAbsolutePath(query)) return null; + if (!/\.\w+$/.test(query)) return null; try { const exists = await trpcClient.fs.fileExists.query({ filePath: query }); @@ -60,7 +66,7 @@ async function getAbsolutePathSuggestion( const fileItem = pathToFileItem(query); return { id: query, - label: fileItem.name, + label: parentDirLabel(fileItem.dir, fileItem.name), description: fileItem.dir || undefined, filename: fileItem.name, path: query, @@ -82,21 +88,16 @@ export async function getFileSuggestions( const { files, fzf } = await fetchRepoFiles(repoPath); const matched = searchFiles(fzf, files, query); - const results: FileSuggestionItem[] = matched.map((file) => { - const parentDir = file.dir ? file.dir.split("/").pop() : undefined; - const label = parentDir ? `${parentDir}/${file.name}` : file.name; - - return { - id: file.path, - label, - description: file.dir || undefined, - filename: file.name, - path: file.path, - }; - }); + const results: FileSuggestionItem[] = matched.map((file) => ({ + id: file.path, + label: parentDirLabel(file.dir, file.name), + description: file.dir || undefined, + filename: file.name, + path: file.path, + })); const resolved = await absolutePathSuggestion; - if (resolved && !results.some((r) => r.id === resolved.id)) { + if (resolved && !results.some((r) => `${repoPath}/${r.id}` === resolved.id)) { results.unshift(resolved); } From e9b6f1d5290c4bb37a465109562245a94d6aecbb Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Thu, 2 Apr 2026 15:36:59 -0700 Subject: [PATCH 4/5] Show parent directory in rendered file mention chips --- .../components/session-update/parseFileMentions.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/code/src/renderer/features/sessions/components/session-update/parseFileMentions.tsx b/apps/code/src/renderer/features/sessions/components/session-update/parseFileMentions.tsx index 6079b3ec8..46f693fb7 100644 --- a/apps/code/src/renderer/features/sessions/components/session-update/parseFileMentions.tsx +++ b/apps/code/src/renderer/features/sessions/components/session-update/parseFileMentions.tsx @@ -91,12 +91,15 @@ export function parseMentionTags(content: string): ReactNode[] { if (match[1]) { const filePath = match[1]; - const fileName = filePath.split("/").pop() ?? filePath; + const segments = filePath.split("/").filter(Boolean); + const fileName = segments.pop() ?? filePath; + const parentDir = segments.pop(); + const label = parentDir ? `${parentDir}/${fileName}` : fileName; parts.push( } - label={fileName} + label={label} />, ); } else if (match[2]) { From e3bc1bea5c8b6aef6d298dbf89434c225b1cd010 Mon Sep 17 00:00:00 2001 From: Charles Vien Date: Thu, 2 Apr 2026 17:10:32 -0700 Subject: [PATCH 5/5] Remove redundant fileExists check from absolute path suggestions --- apps/code/src/main/services/fs/schemas.ts | 1 - apps/code/src/main/services/fs/service.ts | 9 ------- apps/code/src/main/trpc/routers/fs.ts | 6 ----- .../suggestions/getSuggestions.ts | 25 ++++++------------- 4 files changed, 8 insertions(+), 33 deletions(-) diff --git a/apps/code/src/main/services/fs/schemas.ts b/apps/code/src/main/services/fs/schemas.ts index 2981517b4..fec9d26df 100644 --- a/apps/code/src/main/services/fs/schemas.ts +++ b/apps/code/src/main/services/fs/schemas.ts @@ -29,7 +29,6 @@ const fileEntry = z.object({ export const listRepoFilesOutput = z.array(fileEntry); export const readRepoFileOutput = z.string().nullable(); -export const fileExistsOutput = z.boolean(); export type ListRepoFilesInput = z.infer; export type ReadRepoFileInput = z.infer; diff --git a/apps/code/src/main/services/fs/service.ts b/apps/code/src/main/services/fs/service.ts index 52e0844b8..2e00f0d91 100644 --- a/apps/code/src/main/services/fs/service.ts +++ b/apps/code/src/main/services/fs/service.ts @@ -97,15 +97,6 @@ export class FsService { } } - async fileExists(filePath: string): Promise { - try { - const stat = await fs.promises.stat(path.resolve(filePath)); - return stat.isFile(); - } catch { - return false; - } - } - async readAbsoluteFile(filePath: string): Promise { try { return await fs.promises.readFile(path.resolve(filePath), "utf-8"); diff --git a/apps/code/src/main/trpc/routers/fs.ts b/apps/code/src/main/trpc/routers/fs.ts index 81379c81b..1aede0b35 100644 --- a/apps/code/src/main/trpc/routers/fs.ts +++ b/apps/code/src/main/trpc/routers/fs.ts @@ -1,7 +1,6 @@ import { container } from "../../di/container"; import { MAIN_TOKENS } from "../../di/tokens"; import { - fileExistsOutput, listRepoFilesInput, listRepoFilesOutput, readAbsoluteFileInput, @@ -34,11 +33,6 @@ export const fsRouter = router({ .output(readRepoFileOutput) .query(({ input }) => getService().readAbsoluteFile(input.filePath)), - fileExists: publicProcedure - .input(readAbsoluteFileInput) - .output(fileExistsOutput) - .query(({ input }) => getService().fileExists(input.filePath)), - readFileAsBase64: publicProcedure .input(readAbsoluteFileInput) .output(readRepoFileOutput) diff --git a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts index 68650b554..02a158b7b 100644 --- a/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts +++ b/apps/code/src/renderer/features/message-editor/suggestions/getSuggestions.ts @@ -6,7 +6,6 @@ import { pathToFileItem, searchFiles, } from "@hooks/useRepoFiles"; -import { trpcClient } from "@renderer/trpc/client"; import { isAbsolutePath } from "@utils/path"; import Fuse, { type IFuseOptions } from "fuse.js"; import { useDraftStore } from "../stores/draftStore"; @@ -50,19 +49,10 @@ function parentDirLabel(dir: string, name: string): string { return parent ? `${parent}/${name}` : name; } -async function getAbsolutePathSuggestion( - query: string, -): Promise { +function getAbsolutePathSuggestion(query: string): FileSuggestionItem | null { if (!isAbsolutePath(query)) return null; if (!/\.\w+$/.test(query)) return null; - try { - const exists = await trpcClient.fs.fileExists.query({ filePath: query }); - if (!exists) return null; - } catch { - return null; - } - const fileItem = pathToFileItem(query); return { id: query, @@ -78,11 +68,10 @@ export async function getFileSuggestions( query: string, ): Promise { const repoPath = useDraftStore.getState().contexts[sessionId]?.repoPath; - const absolutePathSuggestion = getAbsolutePathSuggestion(query); + const absoluteMatch = getAbsolutePathSuggestion(query); if (!repoPath) { - const resolved = await absolutePathSuggestion; - return resolved ? [resolved] : []; + return absoluteMatch ? [absoluteMatch] : []; } const { files, fzf } = await fetchRepoFiles(repoPath); @@ -96,9 +85,11 @@ export async function getFileSuggestions( path: file.path, })); - const resolved = await absolutePathSuggestion; - if (resolved && !results.some((r) => `${repoPath}/${r.id}` === resolved.id)) { - results.unshift(resolved); + if ( + absoluteMatch && + !results.some((r) => `${repoPath}/${r.id}` === absoluteMatch.id) + ) { + results.unshift(absoluteMatch); } return results;