From 6994e5c17095dcd287ee49b1c0814322a46850a6 Mon Sep 17 00:00:00 2001 From: gkfirst8 Date: Tue, 13 Oct 2026 11:56:20 +0200 Subject: [PATCH 1/4] fix(client): inline code URL paths no longer render as file chips Inline code like `/images/logo.jpg` became a file chip labelled `logo.jpg` because any span starting with `/` counted as a file once it had an extension. Absolute paths now need a known filesystem root or must sit inside the workspace, matching how markdown links already reject routes. --- apps/web/src/markdown-links.ts | 2 +- packages/shared/src/markdownLinks.test.ts | 10 ++++++++++ packages/shared/src/markdownLinks.ts | 16 ++++++++++++++-- packages/shared/src/threadFindText.ts | 5 ++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/web/src/markdown-links.ts b/apps/web/src/markdown-links.ts index 3aa188d27ec24..4cd0e9fa19764 100644 --- a/apps/web/src/markdown-links.ts +++ b/apps/web/src/markdown-links.ts @@ -48,7 +48,7 @@ export function resolveInlineCodeFileLinkMeta( cwd?: string, baseDir: string | undefined = cwd, ): MarkdownFileLinkMeta | null { - const candidate = inlineCodeFilePathCandidate(codeText); + const candidate = inlineCodeFilePathCandidate(codeText, cwd); if (candidate === null) return null; return resolveMarkdownFileLinkMeta( diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index 94d8945b3fb50..42801d6db173f 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -59,9 +59,19 @@ describe("inlineCodeFilePathCandidate", () => { ["usr/lib/libfoo.so.1", "usr/lib/libfoo.so.1"], ["vendor/jquery-3.6.0.min.js", "vendor/jquery-3.6.0.min.js"], ["./models/glm-5.3", "./models/glm-5.3"], + ["/tmp/image.png", "/tmp/image.png"], + ["/images/ZOOM/provider/te115_2.jpg", null], + ["/api/v1/users.json", null], ])("distinguishes file paths from code and hostnames in %s", (source, candidate) => { expect(inlineCodeFilePathCandidate(source)).toBe(candidate); }); + + it("keeps absolute paths inside a workspace outside the usual roots", () => { + expect(inlineCodeFilePathCandidate("/code/app/src/index.ts", "/code/app")).toBe( + "/code/app/src/index.ts", + ); + expect(inlineCodeFilePathCandidate("/images/logo.png", "/code/app")).toBeNull(); + }); }); describe("parseFileUrlHref", () => { diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index bb518d1f44209..a6737f5c7f9bc 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -1,4 +1,8 @@ -import { isWindowsAbsolutePath, stripSlashPrefixedWindowsDrive } from "./path.ts"; +import { + isWindowsAbsolutePath, + stripSlashPrefixedWindowsDrive, + workspaceRelativeFilePath, +} from "./path.ts"; import { type FilePathPosition, formatFilePathPosition, @@ -175,7 +179,7 @@ function looksLikeHostname(segment: string, hasPosition: boolean): boolean { * Picks path-shaped inline code for the client's markdown file-link resolver. * It does not resolve paths or turn plain prose and fenced code into links. */ -export function inlineCodeFilePathCandidate(codeText: string): string | null { +export function inlineCodeFilePathCandidate(codeText: string, cwd?: string): string | null { const trimmed = codeText.trim(); if (trimmed.length === 0 || INLINE_CODE_DISQUALIFIER_PATTERN.test(trimmed)) return null; @@ -199,6 +203,14 @@ export function inlineCodeFilePathCandidate(codeText: string): string | null { if (VERSION_SUFFIX_PATTERN.test(basename)) return null; if (!hasPosition && !FILE_EXTENSION_PATTERN.test(basename)) return null; } + // `/images/logo.jpg` in code is usually a URL path, not a file. + if ( + candidate.startsWith("/") && + !POSIX_FILE_ROOT_PREFIXES.some((prefix) => candidate.startsWith(prefix)) && + workspaceRelativeFilePath(candidate, cwd) === null + ) { + return null; + } return candidate; } diff --git a/packages/shared/src/threadFindText.ts b/packages/shared/src/threadFindText.ts index ab293fa68ea44..f247a6e0688bc 100644 --- a/packages/shared/src/threadFindText.ts +++ b/packages/shared/src/threadFindText.ts @@ -118,7 +118,9 @@ function markdownThreadFindText( const tree = processor.runSync(processor.parse(markdown)); const paths = [ ...extractMarkdownLinkHrefs(renderCodexFileCitationsAsMarkdown(markdown)), - ...extractInlineCodeSpans(markdown).flatMap((span) => inlineCodeFilePathCandidate(span) ?? []), + ...extractInlineCodeSpans(markdown).flatMap( + (span) => inlineCodeFilePathCandidate(span, cwd) ?? [], + ), ].flatMap((href) => { const target = resolveMarkdownFileLinkTarget(href, cwd); return target ? [splitFilePathPosition(target).path] : []; @@ -154,6 +156,7 @@ function markdownThreadFindText( ) { candidate = inlineCodeFilePathCandidate( (node.children ?? []).map((child) => child.value ?? "").join(""), + cwd, ); } const target = candidate ? resolveMarkdownFileLinkTarget(candidate, cwd) : null; From c6a8d5efa212ea8a8aaa7aba517dc364ceafa214 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sun, 1 Nov 2026 03:29:36 +0000 Subject: [PATCH 2/4] fix(shared): preserve slash-prefixed Windows inline paths --- packages/shared/src/markdownLinks.test.ts | 1 + packages/shared/src/markdownLinks.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index 8c068714b2764..128b7ee57f664 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -29,6 +29,7 @@ describe("inlineCodeFilePathCandidate", () => { ["usr/lib/libfoo.so.1", "usr/lib/libfoo.so.1"], ["vendor/jquery-3.6.0.min.js", "vendor/jquery-3.6.0.min.js"], ["./models/glm-5.3", "./models/glm-5.3"], + ["/D:/Programme/t3code/file.ts", "/D:/Programme/t3code/file.ts"], ["/tmp/image.png", "/tmp/image.png"], ["/images/ZOOM/provider/te115_2.jpg", null], ["/api/v1/users.json", null], diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index dfd453a19da6c..e170772d7accf 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -200,6 +200,7 @@ export function inlineCodeFilePathCandidate(codeText: string, cwd?: string): str // `/images/logo.jpg` in code is usually a URL path, not a file. if ( candidate.startsWith("/") && + !isWindowsAbsolutePath(stripSlashPrefixedWindowsDrive(candidate)) && !POSIX_FILE_ROOT_PREFIXES.some((prefix) => candidate.startsWith(prefix)) && workspaceRelativeFilePath(candidate, cwd) === null ) { From 9c8be8fb838fcb0499bb0350837a5aa92b149a20 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sun, 1 Nov 2026 03:39:15 +0000 Subject: [PATCH 3/4] fix(shared): decode inline paths for workspace containment --- packages/shared/src/markdownLinks.test.ts | 3 +++ packages/shared/src/markdownLinks.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index 128b7ee57f664..6d5470178a5b6 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -44,6 +44,9 @@ describe("inlineCodeFilePathCandidate", () => { "/code/app/src/index.ts", ); expect(inlineCodeFilePathCandidate("/images/logo.png", "/code/app")).toBeNull(); + expect(inlineCodeFilePathCandidate("/code/my%20app/src/main.ts", "/code/my app")).toBe( + "/code/my%20app/src/main.ts", + ); }); }); diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index e170772d7accf..3f1138b85e496 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -202,7 +202,7 @@ export function inlineCodeFilePathCandidate(codeText: string, cwd?: string): str candidate.startsWith("/") && !isWindowsAbsolutePath(stripSlashPrefixedWindowsDrive(candidate)) && !POSIX_FILE_ROOT_PREFIXES.some((prefix) => candidate.startsWith(prefix)) && - workspaceRelativeFilePath(candidate, cwd) === null + workspaceRelativeFilePath(safeDecodeURIComponent(candidate), cwd) === null ) { return null; } From 5313bee123f26bbcf96776d3b1400c5b6e41e27c Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sun, 1 Nov 2026 06:18:44 +0000 Subject: [PATCH 4/4] fix(shared): retain encoded absolute inline file paths --- packages/shared/src/markdownLinks.test.ts | 2 ++ packages/shared/src/markdownLinks.ts | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/shared/src/markdownLinks.test.ts b/packages/shared/src/markdownLinks.test.ts index 6d5470178a5b6..aed637cdeeaac 100644 --- a/packages/shared/src/markdownLinks.test.ts +++ b/packages/shared/src/markdownLinks.test.ts @@ -30,6 +30,8 @@ describe("inlineCodeFilePathCandidate", () => { ["vendor/jquery-3.6.0.min.js", "vendor/jquery-3.6.0.min.js"], ["./models/glm-5.3", "./models/glm-5.3"], ["/D:/Programme/t3code/file.ts", "/D:/Programme/t3code/file.ts"], + ["/D%3A/Programme/other/file.ts", "/D%3A/Programme/other/file.ts"], + ["/%74mp/image.png", "/%74mp/image.png"], ["/tmp/image.png", "/tmp/image.png"], ["/images/ZOOM/provider/te115_2.jpg", null], ["/api/v1/users.json", null], diff --git a/packages/shared/src/markdownLinks.ts b/packages/shared/src/markdownLinks.ts index 3f1138b85e496..7106b64bcc5ea 100644 --- a/packages/shared/src/markdownLinks.ts +++ b/packages/shared/src/markdownLinks.ts @@ -198,11 +198,12 @@ export function inlineCodeFilePathCandidate(codeText: string, cwd?: string): str if (!hasPosition && !FILE_EXTENSION_PATTERN.test(basename)) return null; } // `/images/logo.jpg` in code is usually a URL path, not a file. + const decodedCandidate = safeDecodeURIComponent(candidate); if ( candidate.startsWith("/") && - !isWindowsAbsolutePath(stripSlashPrefixedWindowsDrive(candidate)) && - !POSIX_FILE_ROOT_PREFIXES.some((prefix) => candidate.startsWith(prefix)) && - workspaceRelativeFilePath(safeDecodeURIComponent(candidate), cwd) === null + !isWindowsAbsolutePath(stripSlashPrefixedWindowsDrive(decodedCandidate)) && + !POSIX_FILE_ROOT_PREFIXES.some((prefix) => decodedCandidate.startsWith(prefix)) && + workspaceRelativeFilePath(decodedCandidate, cwd) === null ) { return null; }