Skip to content

Commit b10b4c7

Browse files
committed
fix: keep workspace routes out of file links
1 parent ac232a3 commit b10b4c7

4 files changed

Lines changed: 114 additions & 1 deletion

File tree

src/features/messages/components/Markdown.test.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,78 @@ describe("Markdown file-like href behavior", () => {
313313
expect(onOpenFileLink).not.toHaveBeenCalled();
314314
});
315315

316+
it("keeps workspace settings #L anchors as local routes", () => {
317+
const onOpenFileLink = vi.fn();
318+
render(
319+
<Markdown
320+
value="See [settings](/workspace/settings#L12)"
321+
className="markdown"
322+
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
323+
onOpenFileLink={onOpenFileLink}
324+
/>,
325+
);
326+
327+
const link = screen.getByText("settings").closest("a");
328+
expect(link?.getAttribute("href")).toBe("/workspace/settings#L12");
329+
330+
const clickEvent = createEvent.click(link as Element, {
331+
bubbles: true,
332+
cancelable: true,
333+
});
334+
fireEvent(link as Element, clickEvent);
335+
expect(clickEvent.defaultPrevented).toBe(true);
336+
expect(onOpenFileLink).not.toHaveBeenCalled();
337+
});
338+
339+
it("keeps workspace reviews #L anchors as local routes", () => {
340+
const onOpenFileLink = vi.fn();
341+
render(
342+
<Markdown
343+
value="See [reviews](/workspace/reviews#L9)"
344+
className="markdown"
345+
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
346+
onOpenFileLink={onOpenFileLink}
347+
/>,
348+
);
349+
350+
const link = screen.getByText("reviews").closest("a");
351+
expect(link?.getAttribute("href")).toBe("/workspace/reviews#L9");
352+
353+
const clickEvent = createEvent.click(link as Element, {
354+
bubbles: true,
355+
cancelable: true,
356+
});
357+
fireEvent(link as Element, clickEvent);
358+
expect(clickEvent.defaultPrevented).toBe(true);
359+
expect(onOpenFileLink).not.toHaveBeenCalled();
360+
});
361+
362+
it("does not linkify workspace settings #L anchors in plain text", () => {
363+
const { container } = render(
364+
<Markdown
365+
value="See /workspace/settings#L12 for app settings."
366+
className="markdown"
367+
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
368+
/>,
369+
);
370+
371+
expect(container.querySelector(".message-file-link")).toBeNull();
372+
expect(container.textContent).toContain("/workspace/settings#L12");
373+
});
374+
375+
it("does not turn workspace review #L anchors in inline code into file links", () => {
376+
const { container } = render(
377+
<Markdown
378+
value="Use `/workspace/reviews#L9` to reference the reviews route."
379+
className="markdown"
380+
workspacePath="/Users/sotiriskaniras/Documents/Development/Forks/CodexMonitor"
381+
/>,
382+
);
383+
384+
expect(container.querySelector(".message-file-link")).toBeNull();
385+
expect(container.querySelector("code")?.textContent).toBe("/workspace/reviews#L9");
386+
});
387+
316388
it("does not turn natural-language slash phrases into file links", () => {
317389
const { container } = render(
318390
<Markdown

src/features/messages/components/Markdown.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import remarkGfm from "remark-gfm";
44
import { openUrl } from "@tauri-apps/plugin-opener";
55
import {
66
fromFileUrl,
7+
isKnownLocalWorkspaceRoutePath as isKnownLocalWorkspaceRouteFilePath,
78
normalizeFileLinkPath,
89
parseFileLocation,
910
} from "../../../utils/fileLinks";
@@ -349,6 +350,12 @@ function isLikelyFileHref(
349350
}
350351
if (pathOnly.startsWith("/")) {
351352
if (parsedLocation.line !== null) {
353+
const normalizedPath = pathOnly.replace(/\\/g, "/");
354+
if (
355+
WORKSPACE_ROUTE_PREFIXES.some((prefix) => normalizedPath.startsWith(prefix))
356+
) {
357+
return isLikelyMountedWorkspaceFilePath(normalizedPath, workspacePath);
358+
}
352359
return true;
353360
}
354361
if (hasLikelyFileName(pathOnly)) {
@@ -664,6 +671,9 @@ export function Markdown({
664671
if (!normalizedPath) {
665672
return null;
666673
}
674+
if (isKnownLocalWorkspaceRouteFilePath(normalizedPath)) {
675+
return null;
676+
}
667677
if (!isLinkableFilePath(normalizedPath)) {
668678
return null;
669679
}

src/utils/fileLinks.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,25 @@ export function normalizeFileLinkPath(rawPath: string) {
8383
return formatFileLocation(parsed.path, parsed.line, parsed.column);
8484
}
8585

86+
export function isKnownLocalWorkspaceRoutePath(rawPath: string) {
87+
const normalizedPath = parseFileLocation(rawPath).path.trim().replace(/\\/g, "/");
88+
if (normalizedPath.startsWith("/workspace/")) {
89+
const routeSegment = normalizedPath
90+
.slice("/workspace/".length)
91+
.split("/")
92+
.filter(Boolean)[0];
93+
return routeSegment === "reviews" || routeSegment === "settings";
94+
}
95+
if (normalizedPath.startsWith("/workspaces/")) {
96+
const routeSegment = normalizedPath
97+
.slice("/workspaces/".length)
98+
.split("/")
99+
.filter(Boolean)[1];
100+
return routeSegment === "reviews" || routeSegment === "settings";
101+
}
102+
return false;
103+
}
104+
86105
type FileUrlParts = {
87106
host: string;
88107
pathname: string;

src/utils/remarkFileLinks.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { FILE_LINK_SUFFIX_SOURCE, normalizeFileLinkPath } from "./fileLinks";
1+
import {
2+
FILE_LINK_SUFFIX_SOURCE,
3+
isKnownLocalWorkspaceRoutePath,
4+
normalizeFileLinkPath,
5+
} from "./fileLinks";
26

37
const FILE_LINK_PROTOCOL = "codex-file:";
48
const POSIX_OR_RELATIVE_FILE_PATH_PATTERN =
@@ -93,6 +97,11 @@ function linkifyText(value: string) {
9397
const { path, trailing } = splitTrailingPunctuation(raw);
9498
if (path && isPathCandidate(path, leadingText, previousChar)) {
9599
const normalizedPath = normalizeFileLinkPath(path);
100+
if (isKnownLocalWorkspaceRoutePath(normalizedPath)) {
101+
nodes.push({ type: "text", value: raw });
102+
lastIndex = matchIndex + raw.length;
103+
continue;
104+
}
96105
nodes.push({
97106
type: "link",
98107
url: toFileLink(normalizedPath),
@@ -154,6 +163,9 @@ export function isLinkableFilePath(value: string) {
154163
if (!trimmed) {
155164
return false;
156165
}
166+
if (isKnownLocalWorkspaceRoutePath(trimmed)) {
167+
return false;
168+
}
157169
if (!FILE_PATH_MATCH.test(trimmed)) {
158170
return false;
159171
}

0 commit comments

Comments
 (0)