Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
8ba692c
feat(desktop): make file paths in chat messages clickable
Jun 8, 2026
bb0a32e
feat(desktop): linkify file paths in chat messages
Jun 8, 2026
e310220
fix(desktop): address review feedback for clickable file paths
Jun 18, 2026
8ed41e8
fix(desktop): support file paths with spaces in linkifier
Jun 18, 2026
692f479
fix(desktop): exclude trailing prose from spaced path segments
Jun 18, 2026
18923ab
fix(desktop): make path linkifier linear and skip nested traversal
Jun 18, 2026
dd6b368
fix(desktop): address Codex review and CI typecheck failure
Jun 18, 2026
fc5c4d0
fix(desktop): support Unicode paths and keyboard file links
Jun 18, 2026
435732b
fix(desktop): handle numeric path suffixes and link references
Jun 18, 2026
f7ccad0
fix(desktop): skip link subtrees and parenthesized filenames
Jun 18, 2026
d75a60f
fix(desktop): include commas in filename path segments
Jun 18, 2026
6a20648
fix(desktop): preserve closing parens in path suffixes
Jun 18, 2026
4fc7728
fix(desktop): reject partial paths and untrusted open-file links
Jun 18, 2026
6f5003b
fix(desktop): require longer spaced path segments
Jun 18, 2026
98cd9d6
fix(desktop): support word parentheticals and apostrophes in paths
Jun 18, 2026
28c1b4d
fix(desktop): support colons in timestamped filenames
Jun 18, 2026
89e4145
fix(desktop): support alphanumeric parenthetical filename suffixes
Jun 18, 2026
8ffb75e
fix(desktop): stop prose after parenthesized path suffixes
Jun 18, 2026
79526c8
fix(desktop): link paths wrapped in parentheses or brackets
Jun 19, 2026
0c1eec5
fix(desktop): allow brackets and question marks in path filenames
Jun 19, 2026
a84c9f5
fix(desktop): reject numeric counts after path segments
Jun 19, 2026
b940ba7
fix(desktop): reject capitalized prose after path segments
Jun 19, 2026
ca461aa
fix(desktop): include balanced bracket suffixes in path segments
Jun 19, 2026
2c56380
fix(desktop): include bracket suffixes in spaced path segments
Jun 19, 2026
4d8de80
fix(desktop): link long lowercase spaced filename segments
Jun 19, 2026
93fafd0
fix(desktop): strip line suffixes and link multiword filenames
Jun 19, 2026
ebf26e8
fix(desktop): refine line suffix stripping and tab terminators
Jun 19, 2026
194d051
fix(desktop): preserve bracket suffixes and reject prose parentheticals
Jun 19, 2026
c05a4b3
fix(desktop): broaden line suffix stripping and reject bracket tags
Jun 19, 2026
e3d6839
fix(desktop): reject short counts after capitalized path segments
Jun 19, 2026
c78b04f
fix(desktop): strip punctuation in extension lookahead words
Jun 19, 2026
3a727e5
fix(desktop): link top-level absolute directories
Jun 19, 2026
64c45b6
fix(desktop): link paths in questions and Windows drive dirs
Jun 19, 2026
7fbd240
fix(desktop): require drive separator for Windows paths
Jun 19, 2026
10a76ba
fix(desktop): accept assignment prefixes and newline terminators
Jun 19, 2026
65d41d2
fix(desktop): tighten capitalized and parenthetical continuations
Jun 19, 2026
8d5f57b
fix(desktop): reject prose before slashes and after short basenames
Jun 19, 2026
60e6e8e
fix(desktop): preserve spaced directory names before separators
Jun 19, 2026
f31ed2a
fix(desktop): ignore equals in URL query parameters
Jun 19, 2026
b3a3484
fix(desktop): preserve parenthesized filename suffixes with spaces
Jun 19, 2026
f71168a
fix(desktop): tighten spaced path continuation heuristics
Jun 19, 2026
d69f581
fix(ui): linkify inline parenthesized filename suffixes
Jun 19, 2026
2c7ac42
fix(desktop): allow dots and underscores in parenthesized filename su…
Jun 19, 2026
032955f
fix(ui): tighten path linkification for diagnostics and parentheticals
Jun 19, 2026
a321ea3
fix(ui): reject URL fragment/path params and strip lowercase diagnost…
Jun 19, 2026
bc1dd15
fix(ui): linkify paths with parenthesized directory segments
Jun 19, 2026
c9823ef
fix(ui): reject bracketed URL query keys and uppercase extension look…
Jun 19, 2026
ad9db5a
fix(ui): allow titlecase basename extension lookahead
Jun 19, 2026
d5619de
fix(ui): include hyphen in URL query param key scan
Jun 19, 2026
a53493d
fix(ui): allow apostrophe and Unicode in parenthesized filename suffixes
Jun 19, 2026
7aad701
fix(ui): resolve ESLint escape and support Unicode parenthesized paths
Jun 19, 2026
535be07
fix(ui): stop two-char basename paths from absorbing prose
Jun 19, 2026
cc8048e
fix(ui): reject URL path assignments and hyphenated spaced filenames
Jun 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion ui/desktop/src/components/MarkdownContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const customOneDarkTheme = {
import { Check, Copy } from './icons';
import { wrapHTMLInCodeBlock } from '../utils/htmlSecurity';
import { isProtocolSafe, getProtocol, BLOCKED_PROTOCOLS } from '../utils/urlSecurity';
import { remarkLinkifyPaths, OPEN_FILE_PROTOCOL, isTrustedGeneratedFileLink, decodeFileLinkHref } from '../utils/linkifyPaths';
import { ConfirmationModal } from './ui/ConfirmationModal';
import { defineMessages, useIntl } from '../i18n';

Expand Down Expand Up @@ -197,6 +198,19 @@ const customUrlTransform = (url: string): string => {
return url;
};

function getAnchorText(children: React.ReactNode): string {
if (typeof children === 'string' || typeof children === 'number') {
return String(children);
}
if (Array.isArray(children)) {
return children.map(getAnchorText).join('');
}
if (React.isValidElement<{ children?: React.ReactNode }>(children)) {
return getAnchorText(children.props.children);
}
return '';
};

const MarkdownContent = memo(function MarkdownContent({
content,
className = '',
Expand Down Expand Up @@ -258,7 +272,7 @@ const MarkdownContent = memo(function MarkdownContent({
>
<ReactMarkdown
urlTransform={customUrlTransform}
remarkPlugins={[remarkGfm, remarkBreaks, [remarkMath, { singleDollarTextMath: false }]]}
remarkPlugins={[remarkGfm, remarkBreaks, remarkLinkifyPaths, [remarkMath, { singleDollarTextMath: false }]]}
rehypePlugins={[
[
rehypeKatex,
Expand All @@ -271,6 +285,26 @@ const MarkdownContent = memo(function MarkdownContent({
]}
components={{
a: (props) => {
const href = props.href;
if (href && href.startsWith(OPEN_FILE_PROTOCOL)) {
const filePath = decodeFileLinkHref(href);
const label = getAnchorText(props.children);
if (filePath && isTrustedGeneratedFileLink(href, label)) {
return (
<a
{...props}
href={href}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
window.electron.openPathInExplorer(filePath);
}}
className="file-path-link"
title={`Show in Finder: ${filePath}`}
/>
);
}
}
return (
<a
{...props}
Expand Down
10 changes: 10 additions & 0 deletions ui/desktop/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2779,6 +2779,16 @@ async function appMain() {
}
});

ipcMain.handle('open-path-in-explorer', async (_event, path: string) => {
try {
shell.showItemInFolder(expandTilde(path));
return true;
} catch (error) {
console.error('Error showing path in explorer:', error);
return false;
}
});

ipcMain.handle('launch-app', async (event, gooseApp: GooseApp) => {
try {
const launchingWindow = BrowserWindow.fromWebContents(event.sender);
Expand Down
3 changes: 3 additions & 0 deletions ui/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ type ElectronAPI = {
hasAcceptedRecipeBefore: (recipe: Recipe) => Promise<boolean>;
recordRecipeHash: (recipe: Recipe) => Promise<boolean>;
openDirectoryInExplorer: (directoryPath: string) => Promise<boolean>;
openPathInExplorer: (filePath: string) => Promise<boolean>;
launchApp: (app: GooseApp) => Promise<void>;
refreshApp: (app: GooseApp) => Promise<void>;
closeApp: (appName: string) => Promise<void>;
Expand Down Expand Up @@ -344,6 +345,8 @@ const electronAPI: ElectronAPI = {
recordRecipeHash: (recipe: Recipe) => ipcRenderer.invoke('record-recipe-hash', recipe),
openDirectoryInExplorer: (directoryPath: string) =>
ipcRenderer.invoke('open-directory-in-explorer', directoryPath),
openPathInExplorer: (filePath: string) =>
ipcRenderer.invoke('open-path-in-explorer', filePath),
launchApp: (app: GooseApp) => ipcRenderer.invoke('launch-app', app),
refreshApp: (app: GooseApp) => ipcRenderer.invoke('refresh-app', app),
closeApp: (appName: string) => ipcRenderer.invoke('close-app', appName),
Expand Down
12 changes: 12 additions & 0 deletions ui/desktop/src/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -581,6 +581,18 @@ pre:has(> code.bg-inline-code) {
content: '';
}

a.file-path-link {
color: inherit;
text-decoration: none;
cursor: pointer;
border-bottom: 1px dashed currentColor;
}

a.file-path-link:hover {
border-bottom-style: solid;
opacity: 0.8;
}

.user-message p {
margin-bottom: 0 !important;
}
Expand Down
Loading