Skip to content

Commit e3f23ed

Browse files
committed
fix(messages): handle encoded href paths and tighten relative link detection
1 parent fe1e958 commit e3f23ed

2 files changed

Lines changed: 57 additions & 6 deletions

File tree

src/features/messages/components/Markdown.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -268,13 +268,12 @@ function isLikelyFileHref(url: string) {
268268
if (hasLikelyFileName(trimmed)) {
269269
return true;
270270
}
271-
if (
272-
trimmed.startsWith("~/") ||
273-
trimmed.startsWith("./") ||
274-
trimmed.startsWith("../")
275-
) {
271+
if (trimmed.startsWith("~/")) {
276272
return true;
277273
}
274+
if (trimmed.startsWith("./") || trimmed.startsWith("../")) {
275+
return FILE_LINE_SUFFIX_PATTERN.test(trimmed) || hasLikelyFileName(trimmed);
276+
}
278277
return false;
279278
}
280279

@@ -608,7 +607,7 @@ export function Markdown({
608607
if (isLikelyFileHref(url)) {
609608
const directPath = getLinkablePath(url);
610609
if (directPath) {
611-
return directPath;
610+
return safeDecodeURIComponent(directPath) ?? directPath;
612611
}
613612
}
614613
const decodedUrl = safeDecodeURIComponent(url);

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,31 @@ describe("Messages", () => {
261261
expect(openFileLinkMock).toHaveBeenCalledWith(linkedPath);
262262
});
263263

264+
it("decodes percent-encoded href file paths before opening", () => {
265+
const items: ConversationItem[] = [
266+
{
267+
id: "msg-file-href-encoded-link",
268+
kind: "message",
269+
role: "assistant",
270+
text: "Open [guide](./docs/My%20Guide.md)",
271+
},
272+
];
273+
274+
render(
275+
<Messages
276+
items={items}
277+
threadId="thread-1"
278+
workspaceId="ws-1"
279+
isThinking={false}
280+
openTargets={[]}
281+
selectedOpenAppId=""
282+
/>,
283+
);
284+
285+
fireEvent.click(screen.getByText("guide"));
286+
expect(openFileLinkMock).toHaveBeenCalledWith("./docs/My Guide.md");
287+
});
288+
264289
it("keeps non-file relative links as normal markdown links", () => {
265290
const items: ConversationItem[] = [
266291
{
@@ -288,6 +313,33 @@ describe("Messages", () => {
288313
expect(openFileLinkMock).not.toHaveBeenCalled();
289314
});
290315

316+
it("keeps dot-relative non-file links as normal markdown links", () => {
317+
const items: ConversationItem[] = [
318+
{
319+
id: "msg-help-dot-relative-href-link",
320+
kind: "message",
321+
role: "assistant",
322+
text: "See [Help](./help/getting-started)",
323+
},
324+
];
325+
326+
render(
327+
<Messages
328+
items={items}
329+
threadId="thread-1"
330+
workspaceId="ws-1"
331+
isThinking={false}
332+
openTargets={[]}
333+
selectedOpenAppId=""
334+
/>,
335+
);
336+
337+
const helpLink = screen.getByText("Help").closest("a");
338+
expect(helpLink?.getAttribute("href")).toBe("./help/getting-started");
339+
fireEvent.click(screen.getByText("Help"));
340+
expect(openFileLinkMock).not.toHaveBeenCalled();
341+
});
342+
291343
it("does not crash or navigate on malformed codex-file links", () => {
292344
const items: ConversationItem[] = [
293345
{

0 commit comments

Comments
 (0)