Skip to content

fix(web): normalize Windows file link lookup keys

MacroscopeApp / Macroscope - UI Consistency failed Aug 24, 2026 in 2m 55s

UI Consistency: 1 issue found

apps/web/src/components/ChatMarkdown.tsx — the new drive-path separator normalization applied to markdown link hrefs (normalizeMarkdownLinkHrefKey, remarkNormalizeLinksAndTagInlineCode) is not applied to inline-code file references, whose filePath keeps backslashes. Since buildFileLinkParentSuffixByPath groups and fileLinkChip looks up by the raw filePath string, the same Windows file mentioned once as a link and once in inline code is now counted as two distinct same-basename paths, so both chips render a full disambiguating parent suffix (e.g. main.ts · C:/p/src). This chip-label regression is newly reachable because the link chip previously never rendered (href was sanitized away).

Inline comment posted with the smallest suggested fix (normalize separators where the suffix map is built/read, or normalize inline-code metadata the same way).

The earlier finding from the previous run (link href vs. lookup-key mismatch) is resolved by commit 228c58c and the added parseRawHtml-parameterized tests.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.

Files examined: apps/web/src/components/ChatMarkdown.tsx, apps/web/src/components/ChatMarkdown.test.tsx, apps/web/src/markdown-links.ts, apps/web/src/index.css (no changes), apps/web/src/markdown-clipboard.ts.

Verification performed:

  • Traced both key paths for a backslash drive link: raw text → extractMarkdownLinkHrefs → normalizeMarkdownLinkHrefKey (now forward-slash) vs. rendered href → remarkNormalizeLinksAndTagInlineCode (file:///C:/...) → markdownUrlTransform/rewriteMarkdownFileUriHref → normalizeMarkdownLinkHrefKey. Keys now agree, matching the new test that asserts distinct parent suffixes.
  • Checked resolveInlineCodeFileLinkMeta in markdown-links.ts: drive/UNC candidates intentionally retain backslashes, so filePath differs in separator form from the link path while pathParentSegments normalizes separators — producing an all-depth collision and a maximal suffix for both chips.
  • No styling, CSS, theme, primitive, or scroll ownership changes in this diff: chat-markdown-file-link selectors in index.css, MARKDOWN_FILE_LINK_CLASS_NAME, and the sanitize schema's file: protocol allowance are unchanged. No shared-primitive or Tailwind-ownership findings.
  • Tests: the added cases cover both remark plugin lists (lineBreaks and parseRawHtml) and unsafe-scheme rejection, which is the appropriate gate for this behavioral change.

🔍 Agent activity — 10 tool calls

💭 I need to check the actual code for this PR about Windows file links in ChatMarkdown, since the earlier comment flagged a backslash normalization mismatch at line 369. It looks like the diff now includes a change to normalizeMarkdownLinkHrefKey using a Windows drive path regex with replaceAll, which seems to address that earlier concern, so I should verify whether it's actually resolved by looking at the current file.

1. git_tools — normalizeMarkdownLinkHrefKey|rewriteMarkdownFileUriHref|markdownFileLinkMetaByHref|WINDOWS_DRIVE_PATH_REGEX|chat-markdown-file-link

2. git_tools — e6a109b..228c58c

💭 The new commit seems to address my prior comment. I want to check that normalizeMarkdownLinkHrefKey now correctly replaces backslashes when the href starts with a drive path, so I'm reviewing the relevant code sections.

3. browse_code — apps/web/src/components/ChatMarkdown.tsx

4. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 I'm tracing how the anchor renderer normalizes hrefs after remark rewriting, checking whether Windows-style file paths like C:\Users\... get consistently converted through rewriteMarkdownFileUriHref versus the raw text path used when building the destination map.

5. browse_code — apps/web/src/markdown-links.ts

💭 I'm checking that extractMarkdownLinkHrefs only handles inline links, not reference-style definitions — that's fine since reference links fall back to the resolver anyway. Now I'm looking at the a renderer code to see how the metadata gets used for chip rendering and the file link parent suffix.

6. browse_code — apps/web/src/components/ChatMarkdown.tsx

7. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 I'm confirming that since backslashes now normalize to forward slashes for drive paths, the filePath used in map building and rendering stays consistent, and now I need to check how the sanitize step rewrites the link URL into a file:/// format. I'm checking whether rehype-sanitize permits file: URLs, and whether markdownUrlTransform correctly converts them back to a plain drive path for the rendered href — both remark plugin lists appear to handle this consistently, including for image nodes with drive paths. One edge case worth checking is how spaces or special characters in a path (like <C:\Users\my file.ts>) survive the conversion into a file:/// URL and back. I trace through it: wrapping into a file:// URL percent-encodes the space, so the rendered href ends up with %20 while the raw map key retains the literal space — a potential mismatch. But since the fallback resolution path decodes the percent-encoding before comparing, the filePath still matches the map values correctly, so the chip and suffix lookup still work despite the encoding discrepancy. This edge case is pre-existing behavior for POSIX file: links, so it's a minor concern rather than a new bug. Now I want to check the broader scope for UI consistency — shared components, Tailwind usage, CSS ownership. This PR looks like a pure markdown link normalization logic change with tests, and the CSS classes and index.css appear untouched, so there's likely no UI consistency issue here. I want to verify the test file's mocks align properly with the actual import paths in ChatMarkdown.tsx, particularly whether the module specifier for the pull request link hook resolves correctly regardless of alias versus relative path usage. I'm now considering whether rewriting non-file links to file:/// could affect how something like a Windows drive path (e.g. E:/whatever) that isn't recognized as a file candidate gets rendered—falling back to an external anchor with the raw drive-scheme href, which previously would have been stripped by sanitization. That seems like the actual bug this change addresses, since drive-scheme URLs resembling C: paths were likely being sanitized away before. I'm also checking the regex pattern used to detect drive-style paths, noting an edge case where something like d:alert(1) wouldn't match without a slash, though d:/alert(1) would—minor but not really a UI consistency issue. I want to double check the fileLinkChip and MARKDOWN_FILE_LINK styling classes too, though those seem unaffected by this diff, and I want to confirm the second commit fully resolves the earlier finding. Tracing through more path scenarios: I confirm that a drive path with a hash fragment like "C:\p\a.ts#L42" resolves correctly to a matching key after the rewrite and URL parsing round-trip. Then I consider UNC paths like \\server\share\file.ts, which aren't caught by the drive-letter regex, so they'd remain unhandled—likely a pre-existing issue outside the scope of this PR. I check whether markdown-links already handles UNC separately, and note this is a functional gap that's arguably not a new regression. I'm looking again at the test file to make sure I haven't missed a relevant assertion, then turn to how normalizeMarkdownLinkHrefKey behaves when a href is already written as file:///C:/... literally with backslashes. I want to verify whether the anchor renderer receives the href after urlTransform or the raw url — react-markdown likely applies markdownUrlTransform as the urlTransform, so I should check the sanitize schema to confirm file: protocol links are actually allowed through.

8. browse_code — apps/web/src/components/ChatMarkdown.tsx

💭 The file protocol allowance for href/src looks pre-existing rather than something changed in this diff. I'm noticing that the Windows path normalization only applies to images, not raw HTML anchor tags—so a raw <a href="C:\..."> link would still get stripped even though images get special handling. This creates an ownership asymmetry: image normalization happens at the hast level (covering both markdown and raw HTML), while link normalization happens at the mdast level (covering only markdown syntax). It's a real functional gap, but it sits outside the rubric's focus on shared controls, Tailwind, CSS, and layout, so I'm weighing whether it's worth flagging as a violation versus just a design inconsistency between two owners handling the same underlying concern. Working through the sanitize logic, C:/... style paths get stripped too since the colon comes before the first slash, so raw HTML anchors with Windows drive paths lose their href regardless of slash direction. This is a narrow edge case though—the PR's focus is markdown links, not raw HTML anchors—so I'm weighing whether flagging this dual-mechanism inconsistency counts as speculative given the guidance to avoid overreaching findings. Since the output must ultimately conform to a JSON schema despite conflicting formatting instructions, I'll express "no genuine finding" through the schema itself, setting the state to success with a summary of "All clear." Before finalizing, I want to check the in-scope test file that mocks the theme hook to return only resolvedTheme, verifying it doesn't hide any interaction-related gaps that the rubric would want covered by focused tests. Checking the test coverage, I confirm both plugin configurations are exercised through the parameterized cases, and unsafe scheme handling is properly asserted. I want to verify that the fix for the file-link parent suffix mapping is genuinely validated by the backslash-path distinguishing test, and it appears the assertion on both suffixes rendering does confirm the map keys align correctly, so I'm checking the inline code path next to see if drive-letter paths preserve backslashes consistently. Since normalizing path separators makes the parent segments identical between the backslash and forward-slash forms, the two paths would actually collapse into a single unique path rather than triggering the spurious disambiguation suffix I was worried about, so this potential UI inconsistency doesn't actually occur. This is a real scenario with mixed backslash/forward-slash paths on Windows in code spans versus links. Checking the display logic for the markdown-link chip — it computes the relative path using the workspace cwd, and since the target path uses forward slashes here, the relative path resolution works correctly in that case. Actually the key bug: filePath keys get compared as raw strings when building parent suffixes, but link paths are now forward-slash-normalized while inline-code paths still use backslashes, so the same file gets counted twice and both chips show spurious parent suffixes. The cleanest fix is to normalize separators when grouping by key, either in that suffix-building step or in the inline-code path resolution itself. I'm weighing whether this specific chip-labeling inconsistency is really what the UI consistency check is meant to catch, since it's more of a subtle regression in label matching than a visual layout issue.

9. git_tools — fileLinkParentSuffixByPath|parentSuffix

💭 I realize the lookup relies on exact filePath string matching, so an inline code span with backslashes and a markdown link with normalized forward slashes for the same file would produce two distinct filePath strings even though they share the basename "main.ts" — meaning they'd be treated as separate files, each needing a parent-path suffix to disambiguate. Since the shared segments are identical, both chips would end up labeled with the same long parent suffix — exactly the collision the disambiguation logic is meant to prevent, but now triggered spuriously for a single file. This regression only exists because markdown links now render as chips instead of being sanitized away, so it's a real behavior change worth flagging with an inline comment, likely near the normalizeMarkdownLinkHrefKey change or the mdast rewrite, distinct from the earlier key-mismatch comment already there. I trace through resolveInlineCodeFileLinkMeta and confirm the inline-code path preserves backslashes through the whole chain — target resolution, path/position splitting, and basename extraction all handle it fine, so the chip label itself resolves correctly, meaning the divergence is really about the suffix computation rather than basename display. This is a real Windows-specific edge case worth flagging as a single inline comment with a fix suggestion: normalize path separators both when building the parent-suffix map and when looking it up. Since the fix spans two different locations in the same file rather than adjacent lines, I can't give a clean inline diff suggestion, so I'll describe the fix concisely in prose instead. I'm settling on placing the comment on the changed lines in ChatMarkdown.tsx around line 954, since markdown-links.ts itself has no diff lines to attach a comment to even though it's technically in scope for the review. I'll pin the comment to lines 954-956, since those are the added lines in the diff, keeping the body concise.

10. modify_pr — One finding: the new link-side separator normalization diverges from the inline-code path, which can add spurious disambiguation suffixes to file-link chips.


Agent Credits: 85 credits