fix(web): keep windows drive paths clickable in chat markdown - #7189
fix(web): keep windows drive paths clickable in chat markdown#7189CDVolvik wants to merge 1 commit into
Conversation
A markdown destination like `D:/tmp/example.md` rendered as styled but dead text, and it failed differently on each of the two pipelines chat uses. Assistant messages leave `parseRawHtml` at its default of true, so they run `rehype-sanitize`, which reads `D:` as an unknown protocol and removes the href before `urlTransform` is reached. User messages pass false, skip the sanitizer, and React Markdown's `defaultUrlTransform` blanks the same destination for the same reason. Either way the anchor arrives without an href and cannot match an entry in `markdownFileLinkMetaByHref`, even though that map has already resolved the path from the raw message text. Both pipelines run remark, so the destination is normalized there: `toFilesystemLinkUrl` maps a drive path to the `file:` URL the sanitizer already allows, and `rewriteMarkdownFileUriHref` maps it back on the way out. `resolveMarkdownFileLinkTarget` already handled these paths, so this is only about getting them that far. Link keys go through the same function, so `M:\dir\file.md` in the source and the `M:/dir/file.md` the renderer receives resolve to one key rather than missing each other on the separator. The anchor resolves a drive destination itself when the metadata map misses. That map is built by scanning the message text for inline links with a plain destination, so balanced parentheses, nested brackets and bracketed destinations with spaces are absent from it; without the fallback those would render as ordinary anchors pointing at a drive path with no file action. The sanitize schema is exported so the tests can render both pipelines with the real configuration; that needs an explicit type annotation because its inferred type is not nameable outside the module. This covers markdown destinations only. A bare `M:\dir\file.md` written as plain text is still not linked, because GFM does not autolink filesystem paths and adding that is a separate change.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Approved 7ddc786 Straightforward bug fix that makes Windows drive paths clickable in chat markdown by rewriting them to You can customize Macroscope's approvability policy. Learn more. |
|
Confirming this on Windows against a real thread, and this branch fixes it cleanly. Repro (T3 Code
|
| destination | main | this PR |
|---|---|---|
C:/…/Alpha%20%26%20Beta/Notes%20-%20August%202026.md |
dead, href="" |
file chip |
<C:/Users/x/My File.md> |
dead, href="" |
file chip |
C:\Users\x\file.md |
dead, href="" |
file chip |
C:/src/main.ts:42 |
dead, href="" |
file chip |
</Users/x/My File.md> |
dead link | dead link |
/Users/x/File.md |
file chip | file chip |
The percent-decoding and the & both survive the round trip into the resolved target.
Two notes
1. The fallback covers more than the description claims. Because the anchor re-resolves when the text scan misses, bracketed destinations containing spaces also start working — on Windows drive paths that is the Windows half of #5158, which currently reads as untouched by this PR. Worth stating explicitly so it is clear what is left: the POSIX half (</Users/x/My File.md>) still renders dead, so #5158 should stay open after this lands.
2. The fallback gate is drive-only. It is conditioned on toFilesystemLinkUrl(normalizedHref), which tests WINDOWS_DRIVE_PATH_PATTERN but not WINDOWS_UNC_PATH_PATTERN. So a UNC destination the text scan cannot read skips the fallback, even though resolveMarkdownFileLinkTarget already resolves UNC paths. Deliberate scope call, or worth widening that gate?
Neither blocks it. Rewriting at the syntax tree so both pipelines are covered by one change is the right shape here, versus patching the sanitizer and urlTransform separately.
A markdown link whose destination is an absolute Windows path renders as styled text with nothing behind it (#6418).
What changed
Both chat render paths drop the destination before the anchor can use it:
parseRawHtmlat its default oftrue, sorehype-sanitizeruns, readsD:as an unknown protocol, and removes the href — beforeurlTransformis reached.false, skip the sanitizer, and React Markdown'sdefaultUrlTransformblanks the same destination for the same reason.Either way the anchor arrives without an href, so it cannot match an entry in
markdownFileLinkMetaByHrefeven though that map has already resolved the path from the raw message text.Both pipelines run remark, so the destination is normalized there:
toFilesystemLinkUrlmaps a drive path to thefile:URL the sanitize schema already allows, and the existingrewriteMarkdownFileUriHrefmaps it back on the way out.resolveMarkdownFileLinkTargetalready handled these paths, so this is only about getting them that far.Two smaller pieces come with it:
M:\dir\file.mdin the source and theM:/dir/file.mdthe renderer receives resolve to one key rather than missing each other on the separator.CHAT_MARKDOWN_SANITIZE_SCHEMAis exported so the tests can render both pipelines with the real configuration; that needs an explicit type annotation because its inferred type is not nameable outside the module.Before / after
Rendered from the real
ChatMarkdowncomponent with the app's built stylesheet. "Before" is the same component with this PR's source hunks reverted toorigin/main. The file-type glyph inside the chip is a runtime SVG sprite the offline render does not load, so it shows as blank space.Emitted markup for
See [Open](D:/tmp/example.md) for the notes.:parseRawHtml: true)<a target="_blank" rel="noopener noreferrer">Open</a><a href="D:/tmp/example.md" class="… chat-markdown-file-link">… example.md</a>parseRawHtml: false)<a href="" target="_blank" rel="noopener noreferrer">Open</a>Scope
Markdown destinations only. A bare
M:\dir\file.mdwritten as plain text is still not linked, because GFM does not autolink filesystem paths and adding that is a separate change. If you read #6418 to include the bare-path case, this does not close it outright.Testing
apps/webunit project: 2574 tests across 262 files passrenderToStaticMarkupwith the real sanitize schema, and that assert an unsafe scheme is still droppedtsgo --noEmitclean;vp lint0 warnings / 0 errors on the changed files;vp fmt --checkcleanorigin/main@4cb676cc1Note
Low Risk
Localized markdown rendering and URL handling for Windows drive paths; unsafe schemes remain rejected and behavior is covered by pipeline tests for both assistant and user render modes.
Overview
Fixes #6418: markdown links like
[Open](D:/tmp/example.md)used to render without a usablehrefbecauseD:was treated as an unknown protocol—assistant messages lost it inrehype-sanitize, and user messages got a blank href fromdefaultUrlTransform.A new
remarkFilesystemLinkDestinationsplugin runs on both chat pipelines and rewrites drive (and reference-definition) destinations to allowedfile:URLs; existingrewriteMarkdownFileUriHrefstill turns them back into plain paths on output.toFilesystemLinkUrlcentralizes that mapping and normalizes\vs/so metadata keys match what the renderer sees.ChatMarkdownwires the plugin in, keys link metadata through the same helper, and falls back toresolveMarkdownFileLinkMetaat render time when the message-text regex scan missed the destination (parentheses, spaced<...>paths, etc.).CHAT_MARKDOWN_SANITIZE_SCHEMAis exported (with an explicit type) so tests can mirror bothparseRawHtmlpaths; new unit tests assert drive links work,javascript:stays dropped, and https links are unchanged.Scope: markdown link destinations only—not autolinking bare paths in plain text.
Reviewed by Cursor Bugbot for commit 7ddc786. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Fix Windows drive paths to remain clickable as file links in chat markdown
remarkFilesystemLinkDestinationsin markdown-filesystem-links.ts, that rewrites Windows drive-style link destinations (e.g.D:/path/file.md, backslash variants) tofile:///URLs before sanitization strips them.toFilesystemLinkUrlin markdown-links.ts to convert Windows drive paths tofile:URLs and normalize path separators.normalizeMarkdownLinkHrefKeyin ChatMarkdown.tsx to key drive destinations via theirfile:form so lookups into the pre-scanned metadata map succeed for both slash and backslash variants.ChatMarkdownnow falls back to resolving file link metadata on the fly when the pre-scan misses a link shape.Macroscope summarized 7ddc786.