Skip to content

fix(chat-ui): render reference-style markdown links and images - #3049

Open
eeshsaxena wants to merge 1 commit into
generalaction:mainfrom
eeshsaxena:fix/markdown-reference-links
Open

fix(chat-ui): render reference-style markdown links and images#3049
eeshsaxena wants to merge 1 commit into
generalaction:mainfrom
eeshsaxena:fix/markdown-reference-links

Conversation

@eeshsaxena

Copy link
Copy Markdown

Summary

Reference-style markdown links and images are dropped when rendering a message.

The block/inline walk in parse.ts handles inline link / image mdast nodes, but reference-style syntax parses to linkReference / imageReference nodes, which hit the default case in phrasingsToRuns and are ignored. The result is that the link's visible text disappears too, leaving a gap:

See [the docs][d] for more.

[d]: https://example.com/docs

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 as remark-inline-mentions.ts) that rewrites linkReferencelink and imageReferenceimage using the document's definition nodes, before the walk. The existing link / image renderers then handle them unchanged, so there are no changes to phrasingsToRuns / blockToBlocks.

remark-parse only 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)

parseMarkdownToBlocks("m", "See [the docs][d].\n\n[d]: https://example.com/docs")
// before: runs = [{ text: "See " }, { text: "." }]                                   ❌ "the docs" dropped
// after:  runs = [{ text: "See " }, { text: "the docs", href: "https://example.com/docs" }, { text: "." }]

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 images block to parse.test.ts. I verified the fix by running the real parseMarkdownToBlocks through tsx against all reference variants (output shown above); the assertions match. I wasn't able to run the full pnpm vitest suite locally (monorepo install was too slow in my environment), so the jsdom Vitest project runs in CI. The change is scoped to packages/chat-ui/src/core/markdown/.

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.
@eeshsaxena

Copy link
Copy Markdown
Author

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-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a remark transform that resolves reference-style Markdown links and images before the existing inline renderer processes them.

  • Registers the reference resolver in the shared Markdown processor.
  • Converts link and image references using document definitions.
  • Adds coverage for full, collapsed, shortcut, image, and undefined references.

Confidence Score: 4/5

The 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

Important Files Changed

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]
Loading
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 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.

Suggested change
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant