fix(ui): fix ./ path prefix for SHIFT+ENTER

This commit is contained in:
VooDisss
2026-02-16 04:29:24 +02:00
parent f58267dd30
commit b31135f622
3 changed files with 81 additions and 34 deletions

View File

@@ -204,13 +204,16 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
}
const folderMention =
relativePath === "." || relativePath === ""
? "/"
: relativePath.replace(/\/+$/, "") + "/"
relativePath === "." || relativePath === "" || relativePath === "./"
? "./"
: (relativePath.startsWith("./") ? relativePath.replace(/\/+$/, "") + "/" : relativePath.replace(/^\.\//, "").replace(/\/+$/, "") + "/")
const normalizedFolderPath = (() => {
const trimmed = relativePath.replace(/\/+$/, "")
return trimmed.length > 0 ? trimmed : "."
// If it's root "./", just return "./"
if (trimmed === "" || trimmed === ".") return "./"
// Otherwise remove any leading ./ and add ./ prefix
return "./" + trimmed.replace(/^\.\//, "")
})()
const addPathOnlyAttachment = (value: string) => {
@@ -237,12 +240,13 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
if (action === "shiftEnter") {
// SHIFT+ENTER on directory: keep @path in prompt, add text attachment, remove @ when sending
addPathOnlyAttachment(folderMention)
// Always prefix with ./ for consistency
const normalizedFolderPathWithPrefix = normalizedFolderPath.startsWith("./") ? normalizedFolderPath : "./" + normalizedFolderPath
addPathOnlyAttachment(normalizedFolderPathWithPrefix)
replaceMentionToken(mentionText, { trailingSpace: true })
} else {
// ENTER/click on directory: attach as a file part pointing at a file:// directory URL.
const dirLabel =
normalizedFolderPath === "." ? "/" : normalizedFolderPath.split("/").pop() || normalizedFolderPath
const dirLabel = normalizedFolderPath === "./" ? "./" : normalizedFolderPath.split("/").pop() || normalizedFolderPath
const dirFilename = dirLabel.endsWith("/") ? dirLabel : `${dirLabel}/`
const existingAttachments = getAttachments(options.instanceId(), options.sessionId())
@@ -275,10 +279,14 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
if (action === "shiftEnter") {
// SHIFT+ENTER on file: keep @path in prompt, add text attachment, remove @ when sending
addPathOnlyAttachment(normalizedPath)
replaceMentionToken(`@${normalizedPath}`, { trailingSpace: true })
// Always prefix with ./ for consistency
const normalizedPathWithPrefix = normalizedPath.startsWith("./") ? normalizedPath : "./" + normalizedPath
addPathOnlyAttachment(normalizedPathWithPrefix)
replaceMentionToken(`@${normalizedPathWithPrefix}`, { trailingSpace: true })
} else {
// ENTER/click on file: attach file (existing behavior).
// Always prefix with ./ for consistency
const normalizedPathWithPrefix = normalizedPath.startsWith("./") ? normalizedPath : "./" + normalizedPath
const pathSegments = normalizedPath.split("/")
const filename = (() => {
const candidate = pathSegments[pathSegments.length - 1] || normalizedPath
@@ -287,12 +295,12 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
const existingAttachments = getAttachments(options.instanceId(), options.sessionId())
const alreadyAttached = existingAttachments.some(
(att) => att.source.type === "file" && att.source.path === normalizedPath,
(att) => att.source.type === "file" && att.source.path === normalizedPathWithPrefix,
)
if (!alreadyAttached) {
const attachment = createFileAttachment(
normalizedPath,
normalizedPathWithPrefix,
filename,
"text/plain",
undefined,
@@ -301,7 +309,7 @@ export function usePromptPicker(options: PromptPickerOptions): PromptPickerContr
addAttachment(options.instanceId(), options.sessionId(), attachment)
}
replaceMentionToken(`@${normalizedPath}`, { trailingSpace: true })
replaceMentionToken(`@${normalizedPathWithPrefix}`, { trailingSpace: true })
}
}
}

View File

@@ -51,9 +51,7 @@ function normalizeQuery(rawQuery: string) {
if (!trimmed) {
return ""
}
if (trimmed === "." || trimmed === "./") {
return ""
}
// Don't normalize "." - it's used for workspace root
return trimmed.replace(/^(\.\/)+/, "").replace(/^\/+/, "")
}
@@ -350,18 +348,22 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
return items
}
// Add root directory as first item when query is "/"
if (mode() === "mention" && props.searchQuery === "/") {
// Add root directory as first item only when query is EXACTLY "." or "./" (not "./docs/")
const isExactRootQuery = props.searchQuery === "." || props.searchQuery === "./"
if (mode() === "mention" && isExactRootQuery) {
const rootFile: FileItem = {
path: "/",
relativePath: "/",
path: ".",
relativePath: ".",
isDirectory: true,
isGitFile: false,
}
items.push({ type: "file", file: rootFile })
}
filteredAgents().forEach((agent) => items.push({ type: "agent", agent }))
// Don't show agents for exact root path queries
if (!isExactRootQuery) {
filteredAgents().forEach((agent) => items.push({ type: "agent", agent }))
}
files().forEach((file) => items.push({ type: "file", file }))
return items
}
@@ -485,7 +487,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
</For>
</Show>
<Show when={mode() === "mention" && agentCount() > 0}>
<Show when={mode() === "mention" && agentCount() > 0 && !(props.searchQuery === "." || props.searchQuery === "./")}>
<div class="dropdown-section-header">
{t("unifiedPicker.sections.agents")}
</div>
@@ -540,11 +542,11 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
</For>
</Show>
<Show when={mode() === "mention" && fileCount() > 0}>
<Show when={mode() === "mention" && (fileCount() > 0 || props.searchQuery === "." || props.searchQuery === "./")}>
<div class="dropdown-section-header">
{props.searchQuery === "/" ? t("unifiedPicker.sections.directories") : t("unifiedPicker.sections.files")}
{t("unifiedPicker.sections.files")}
</div>
<Show when={props.searchQuery === "/"}>
<Show when={props.searchQuery === "." || props.searchQuery === "./"}>
<div
class={`dropdown-item py-1.5 ${
selectedIndex() === 0 ? "dropdown-item-highlight" : ""
@@ -552,8 +554,8 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
data-picker-selected={selectedIndex() === 0}
onClick={() => {
const rootFile: FileItem = {
path: "/",
relativePath: "/",
path: ".",
relativePath: ".",
isDirectory: true,
isGitFile: false,
}
@@ -569,7 +571,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"
/>
</svg>
<span class="font-mono">/ (root)</span>
<span class="font-mono">. (workspace root)</span>
</div>
</div>
</Show>

View File

@@ -23,15 +23,52 @@ export function resolvePastedPlaceholders(prompt: string, attachments: Attachmen
let result = prompt
// For each path attachment (SHIFT+ENTER), find and replace @path with path in the prompt
// We ALWAYS strip @ for SHIFT+ENTER paths, even if there's also a file attachment
for (const path of pathAttachments) {
// Try both with and without trailing slash
const variants = [path, path + "/"]
if (!path) continue
// The path should already have ./ prefix from usePromptPicker
// We need to find @path in prompt and replace with path
// For "./docs/" path, try to match @docs/, @./docs/, @docs, etc.
const basePath = path.replace(/^\.\//, "").replace(/\/+$/, "") // "docs"
const withSlash = basePath + "/" // "docs/"
const patterns = [
"@" + path, // @./docs/
"@" + basePath, // @docs
"@" + withSlash, // @docs/
]
for (const pattern of patterns) {
if (result.includes(pattern)) {
result = result.replace(pattern, path)
}
}
}
for (const variant of variants) {
// Replace @path with path (exact match)
const searchPattern = "@" + variant
result = result.split(searchPattern).join(variant)
// Also strip @ for paths that have file attachments (ENTER case)
for (const filePath of fileAttachments) {
if (!filePath || filePath.length === 0) continue
// Special case: if attachment is "./" or ".", handle separately
if (filePath === "./" || filePath === ".") {
result = result.replace("@./", "./")
result = result.replace("@.", "./")
continue
}
// Normal path handling
const pathToFind = filePath.replace(/^\.\//, "")
const patterns = [
"@" + filePath,
"@./" + pathToFind,
"@" + pathToFind,
]
for (const pattern of patterns) {
if (result.includes(pattern)) {
result = result.replace(pattern, filePath)
}
}
}