Skip to content

feat: render chat markdown links#41

Open
lauridskern wants to merge 1 commit into
fix/hide-system-prompt-tagsfrom
feat/chat-markdown-links
Open

feat: render chat markdown links#41
lauridskern wants to merge 1 commit into
fix/hide-system-prompt-tagsfrom
feat/chat-markdown-links

Conversation

@lauridskern
Copy link
Copy Markdown
Owner

Summary

Add simple Markdown rendering for chat messages with robust URL and file-path link handling.

Context

Chat messages previously relied on plain text / filename-style parsing, which made Markdown output less readable and caused link handling to be inconsistent. External URLs need to open in the system browser, while file references should resolve reliably and open in the IDE or Finder/file manager without crashing on malformed URL-like input.

Changes

  • Render assistant/reasoning chat messages with react-markdown and remark-gfm.
  • Add simple, project-styled Markdown support for paragraphs, headings, lists, tables, blockquotes, inline code, and code blocks.
  • Add a lightweight code-block copy button.
  • Centralize chat URL/file-path parsing in a shared resolver used by both Markdown links and inline plain text.
  • Support robust file path formats including workspace-relative paths, absolute POSIX paths, ~/ paths, Windows drive/UNC paths, file:// URLs, line/column suffixes, and #LxCy fragments.
  • Route external http/https links to the system browser.
  • Route file/path links through the existing open-in-target flow, trying Cursor first and falling back to Finder/file manager.
  • Harden the Rust opener so file metadata is stripped safely before opening paths.

Key Implementation Details

The link resolver borrows the defensive parts of t3code’s approach while keeping our UI simple: safe percent decoding, guarded file:// parsing, query/hash stripping, external-scheme rejection, POSIX root guardrails, and workspace-relative resolution. The backend opener now mirrors the same cleanup as a safety net before resolving paths against the workspace.

Use Cases

  • Assistant response includes https://github.com/... and clicking it opens the browser.
  • Assistant response includes src/components/chat/ChatMarkdown.tsx:42 and clicking it opens the file in Cursor/Finder.
  • Markdown links such as [file](file:///Users/me/project/src/main.tsx#L12C4) resolve as file links instead of browser links.
  • Malformed URL-like file text renders safely instead of crashing the chat UI.

Testing

Validated locally with:

bun run build
cargo check

Manual verification should cover:

  1. Send a message with a normal URL and confirm it opens in the browser.
  2. Send a message with a workspace-relative file path and confirm it opens in Cursor or Finder.
  3. Send Markdown links with both https://... and file://... hrefs and confirm each opens through the correct path.
  4. Send malformed percent-encoded path-like text and confirm chat rendering does not crash.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51e7d264df

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +216 to +219
let without_fragment = without_scheme.split('#').next().unwrap_or(&without_scheme);
let without_query = without_fragment
.split('?')
.next()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve literal '#' and '?' in non-URL file paths

The new sanitization in strip_file_path_metadata now unconditionally splits every incoming path on # and ?, even when the input is already a plain filesystem path rather than a file:// URL. On POSIX systems those characters are valid filename characters, so opening files such as notes#draft.md or query?plan.sql via open_path_in_target will resolve to the wrong truncated path and fail. This is a regression for existing callers (for example file-diff open actions) that pass raw repo paths.

Useful? React with 👍 / 👎.

const EXTERNAL_SCHEME_PATTERN = /^([A-Za-z][A-Za-z0-9+.-]*):(.*)$/u;
const RELATIVE_PATH_PREFIX_PATTERN = /^(~\/|\.{1,2}\/)/u;
const RELATIVE_FILE_PATH_PATTERN = /^[A-Za-z0-9._@-]+(?:\/[A-Za-z0-9._@-]+)+(?::\d+){0,2}$/u;
const RELATIVE_FILE_NAME_PATTERN = /^[A-Za-z0-9._@-]+\.[A-Za-z0-9_-]+(?::\d+){0,2}$/u;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid treating domain-like tokens as local file paths

The current filename heuristic accepts any dotted token (RELATIVE_FILE_NAME_PATTERN), so plain text like example.com is parsed as a local file and resolved to ${workspacePath}/example.com instead of being left as text or a URL. In chat output this creates false-positive clickable file links that attempt to open non-existent files, which is especially common when assistants mention hostnames without http://.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant