fix(chat-ui): render reference-style markdown links and images - #3049
fix(chat-ui): render reference-style markdown links and images#3049eeshsaxena wants to merge 1 commit into
Conversation
The markdown block/inline walk handles inline `link`/`image` nodes but not `linkReference`/`imageReference`, so a reference-style link or image such as `[the docs][d]` (with `[d]: <url>` defined) hit the default case and was dropped entirely — its visible text vanished, leaving a gap in the rendered message. Add a small remark plugin that resolves reference links/images to their inline `link`/`image` equivalents from the document definitions before the walk, so the existing renderers handle them. remark-parse only emits reference nodes when a definition exists (an undefined reference is already plain text), so every reference resolves. Inline links are unchanged; undefined references stay literal. Covers full, collapsed, and shortcut references plus image references.
|
This is a silent content-loss bug rather than a styling one: the link text disappears entirely, so a message that says "see [the docs][d]" renders as "see ." with a gap, and the reader has no idea a link was there. Reference-style links show up a lot in AI/agent output and pasted docs, which is why it is worth handling. The fix stays out of the walk itself by resolving references to inline nodes in a pre-pass. |
Greptile SummaryThe PR adds a remark transform that resolves reference-style Markdown links and images before the existing inline renderer processes them.
Confidence Score: 4/5The PR should not merge until duplicate reference definitions preserve CommonMark's first-definition-wins behavior. The resolver unconditionally overwrites definitions with the same normalized identifier, causing affected links and images to render the later URL rather than the destination required by Markdown semantics. Files Needing Attention: packages/chat-ui/src/core/markdown/remark-resolve-references.ts
|
| Filename | Overview |
|---|---|
| packages/chat-ui/src/core/markdown/remark-resolve-references.ts | Adds reference resolution, but duplicate normalized definitions incorrectly use the last definition instead of the first. |
| packages/chat-ui/src/core/markdown/parse.ts | Registers the new resolver before mention processing so existing link and image rendering can consume converted nodes. |
| packages/chat-ui/src/core/markdown/parse.test.ts | Covers the principal reference forms and undefined references, but omits duplicate-definition precedence. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart LR
Markdown[Markdown message] --> Parse[remarkParse]
Parse --> Definitions[Collect definitions]
Definitions --> Resolve[Resolve link and image references]
Resolve --> Walk[Existing block and inline walk]
Walk --> Runs[Rendered inline runs]
Prompt To Fix All With AI
### Issue 1
packages/chat-ui/src/core/markdown/remark-resolve-references.ts:22
**Duplicate definitions resolve incorrectly**
When multiple definitions share the same normalized identifier, the unconditional `Map.set` replaces the first definition with the last, causing reference links and images to use the wrong destination instead of CommonMark's first matching definition.
```suggestion
if (!definitions.has(node.identifier.toLowerCase())) {
definitions.set(node.identifier.toLowerCase(), node);
}
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.Reviews (1): Last reviewed commit: "fix(chat-ui): render reference-style mar..." | Re-trigger Greptile
| return (tree: Root): void => { | ||
| const definitions = new Map<string, Definition>(); | ||
| visit(tree, 'definition', (node) => { | ||
| definitions.set(node.identifier.toLowerCase(), node); |
There was a problem hiding this comment.
Duplicate definitions resolve incorrectly
When multiple definitions share the same normalized identifier, the unconditional Map.set replaces the first definition with the last, causing reference links and images to use the wrong destination instead of CommonMark's first matching definition.
| definitions.set(node.identifier.toLowerCase(), node); | |
| if (!definitions.has(node.identifier.toLowerCase())) { | |
| definitions.set(node.identifier.toLowerCase(), node); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/chat-ui/src/core/markdown/remark-resolve-references.ts
Line: 22
Comment:
**Duplicate definitions resolve incorrectly**
When multiple definitions share the same normalized identifier, the unconditional `Map.set` replaces the first definition with the last, causing reference links and images to use the wrong destination instead of CommonMark's first matching definition.
```suggestion
if (!definitions.has(node.identifier.toLowerCase())) {
definitions.set(node.identifier.toLowerCase(), node);
}
```
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Summary
Reference-style markdown links and images are dropped when rendering a message.
The block/inline walk in
parse.tshandles inlinelink/imagemdast nodes, but reference-style syntax parses tolinkReference/imageReferencenodes, which hit thedefaultcase inphrasingsToRunsand are ignored. The result is that the link's visible text disappears too, leaving a gap:Before this change that rendered as
See for more.— "the docs" and its URL both gone.Fix
Add a small remark plugin (
remark-resolve-references.ts, in the same style asremark-inline-mentions.ts) that rewriteslinkReference→linkandimageReference→imageusing the document'sdefinitionnodes, before the walk. The existinglink/imagerenderers then handle them unchanged, so there are no changes tophrasingsToRuns/blockToBlocks.remark-parseonly emits reference nodes when a matching definition exists (an undefined reference like[text][missing]is already plain text), so every reference resolves. Identifier matching is case-insensitive per CommonMark.Before / after (real parser output)
Covers full (
[t][id]), collapsed ([id][]), and shortcut ([id]) references, plus image references (![alt][id]). Inline links ([t](url)) are unchanged; undefined references stay literal.Testing
Added a
reference-style links and imagesblock toparse.test.ts. I verified the fix by running the realparseMarkdownToBlocksthroughtsxagainst all reference variants (output shown above); the assertions match. I wasn't able to run the fullpnpmvitest suite locally (monorepo install was too slow in my environment), so the jsdom Vitest project runs in CI. The change is scoped topackages/chat-ui/src/core/markdown/.