Skip to content

MarkdownRenderable: bare URLs duplicated as 'url (url)' in concealed mode #1050

Closed as duplicate

Description

@hackerh3

Bug

When conceal is true (the default for MarkdownRenderable), bare URLs in markdown content render as https://example.com (https://example.com) — the URL text followed by the same URL in parentheses.

This happens because the link case in renderInlineToken unconditionally appends (href) after the link label. For named links like [click here](https://example.com), this renders correctly as click here (https://example.com). But for bare/autolinked URLs, the link text is the href, producing duplication.

Location

packages/core/src/renderables/Markdown.ts — the "link" case in renderInlineToken:

case "link": {
  const linkHref = { url: token.href }
  if (this._conceal) {
    for (const child of token.tokens) {
      this.renderInlineTokenWithStyle(child, chunks, "markup.link.label", linkHref)
    }
    // Always appends (href), even when link text === href
    chunks.push(this.createChunk(" (", "markup.link", linkHref))
    chunks.push(this.createChunk(token.href, "markup.link.url", linkHref))
    chunks.push(this.createChunk(")", "markup.link", linkHref))
  }
}

Fix

Skip the (href) suffix when the link text equals the href:

case "link": {
  const linkHref = { url: token.href }
  if (this._conceal) {
    const linkText = token.tokens.map(t => t.raw).join("")
    const isBareUrl = linkText === token.href
    for (const child of token.tokens) {
      this.renderInlineTokenWithStyle(child, chunks, "markup.link.label", linkHref)
    }
    if (!isBareUrl) {
      chunks.push(this.createChunk(" (", "markup.link", linkHref))
      chunks.push(this.createChunk(token.href, "markup.link.url", linkHref))
      chunks.push(this.createChunk(")", "markup.link", linkHref))
    }
  }
}

Reproduction

Any markdown with a bare URL:

Check https://github.com/anomalyco/opentui for details.

Renders as: Check https://github.com/anomalyco/opentui (https://github.com/anomalyco/opentui) for details.

Expected: Check https://github.com/anomalyco/opentui for details.

This also affects markdown tables where bare URLs are common — the duplication wastes column width and degrades readability.

Affected version

@opentui/core 0.2.6 (and likely since 0.1.88 when conceal was introduced via #767)

Related

Activity

  1. ash543210 commented on May 30, 2026

    @ash543210

    I'd like to help with this, but I noticed #1081 already implemented the token.text === token.href fix and was closed without merging. Before opening another PR I'd rather understand the preferred direction:

    • Is the duplication considered acceptable / working-as-intended for now?
    • Is the conceal-mode link rendering being reworked as part of the text-flow changes mentioned in Feature Request: Support for center aligned text #916, so spot-fixes here aren't wanted yet?
    • Or would a different approach be preferred (e.g. a config option, or handling it at the table/detectLinks layer rather than in the link conceal branch)?

    Happy to implement whichever you'd prefer. Thanks!

  2. epicwhale commented on Jun 26, 2026

    @epicwhale

    I face this issue all the time and it would be good to have better usability around how links are shown, made clickable, and reduced duplication/noise in the TUI output

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions