From d32754364a293ed46968ca3ae53af20782aa5d9e Mon Sep 17 00:00:00 2001 From: Maansen Date: Sat, 28 Mar 2026 10:42:38 +0100 Subject: [PATCH] fix(web): linkify Windows C:/ paths in terminal output FILE_PATH_PATTERN only treated drive roots as X:\\, so lines like C:/Users/.../file.ts were skipped even though path resolution already accepts forward slashes after the colon. Tests cover extraction, trailing punctuation, and resolvePathLinkTarget. wsTransport test matches any SyntaxError prefix so Bun's JSON.parse message doesn't fail vitest. Made-with: Cursor --- apps/web/src/terminal-links.test.ts | 32 +++++++++++++++++++++++++++++ apps/web/src/terminal-links.ts | 2 +- apps/web/src/wsTransport.test.ts | 2 +- 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/apps/web/src/terminal-links.test.ts b/apps/web/src/terminal-links.test.ts index 1e5fb39405..db0544fcef 100644 --- a/apps/web/src/terminal-links.test.ts +++ b/apps/web/src/terminal-links.test.ts @@ -43,6 +43,32 @@ describe("extractTerminalLinks", () => { }, ]); }); + + it("finds Windows absolute paths with forward slashes", () => { + const line = "see C:/Users/someone/project/src/file.ts:42 for details"; + const path = "C:/Users/someone/project/src/file.ts:42"; + const start = line.indexOf(path); + expect(extractTerminalLinks(line)).toEqual([ + { + kind: "path", + text: path, + start, + end: start + path.length, + }, + ]); + }); + + it("trims trailing punctuation from Windows forward-slash paths", () => { + const line = "(C:/tmp/x.ts)."; + expect(extractTerminalLinks(line)).toEqual([ + { + kind: "path", + text: "C:/tmp/x.ts", + start: 1, + end: 12, + }, + ]); + }); }); describe("resolvePathLinkTarget", () => { @@ -60,6 +86,12 @@ describe("resolvePathLinkTarget", () => { resolvePathLinkTarget("/Users/julius/project/src/main.ts:12", "/Users/julius/project"), ).toBe("/Users/julius/project/src/main.ts:12"); }); + + it("keeps Windows absolute paths with forward slashes unchanged", () => { + expect( + resolvePathLinkTarget("C:/Users/julius/project/src/main.ts:12", "C:\\Users\\julius\\project"), + ).toBe("C:/Users/julius/project/src/main.ts:12"); + }); }); describe("isTerminalLinkActivation", () => { diff --git a/apps/web/src/terminal-links.ts b/apps/web/src/terminal-links.ts index 1119fc9087..e07904e180 100644 --- a/apps/web/src/terminal-links.ts +++ b/apps/web/src/terminal-links.ts @@ -11,7 +11,7 @@ export interface TerminalLinkMatch { const URL_PATTERN = /https?:\/\/[^\s"'`<>]+/g; const FILE_PATH_PATTERN = - /(?:~\/|\.{1,2}\/|\/|[A-Za-z]:\\|\\\\)[^\s"'`<>]+|[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+(?::\d+){0,2}/g; + /(?:~\/|\.{1,2}\/|\/|[A-Za-z]:[\\/]|\\\\)[^\s"'`<>]+|[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+(?::\d+){0,2}/g; const TRAILING_PUNCTUATION_PATTERN = /[.,;!?]+$/; function trimClosingDelimiters(value: string): string { diff --git a/apps/web/src/wsTransport.test.ts b/apps/web/src/wsTransport.test.ts index e66ed7fc0d..c9b963286b 100644 --- a/apps/web/src/wsTransport.test.ts +++ b/apps/web/src/wsTransport.test.ts @@ -175,7 +175,7 @@ describe("WsTransport", () => { expect(warnSpy).toHaveBeenNthCalledWith( 1, "Dropped inbound WebSocket envelope", - "SyntaxError: Expected property name or '}' in JSON at position 2 (line 1 column 3)", + expect.stringMatching(/^SyntaxError:/), ); expect(warnSpy).toHaveBeenNthCalledWith( 2,