Skip to content

[Detail Bug] Markdown: Inline code with enrichment markers renders as enrichment card in frontend (backend parity break) #156

Description

@detail-app

Detail Bug Report

https://app.detail.dev/org_befd6425-a158-4e24-9d4d-1e5c08769515/bugs/bug_51a9df6e-e9f3-47a5-a995-c0890034b522

Introduced in #3 by @WilliamAGH on Jan 24, 2026

Summary

  • Context: Enrichment markers ({{hint:...}}, {{warning:...}}, etc.) are part of the public API and are expected to work in code examples where users write `{{hint:...}}` to show literal syntax.
  • Bug: Frontend incorrectly renders `{{hint:test}}` as an enrichment card.
  • Actual vs. expected: Actual: enrichment card is rendered and the backticks are orphaned. Expected: the marker remains literal inside inline code: <code>{{hint:test}}</code>.
  • Impact: Frontend markdown output diverges from backend behavior; documentation/code examples that include enrichment marker syntax inside inline code are rendered incorrectly in the UI.

Code with Bug

frontend/src/lib/services/markdown.ts

function findEnrichmentStart(src: string): number {
  let openingIndex = -1;
  for (const kind of ENRICHMENT_PRESENTATIONS_BY_TOKEN.keys()) {
    const candidateIndex = src.indexOf(`{{${kind}:`);  // <-- BUG 🔴 no check for inline-code context
    if (candidateIndex >= 0 && (openingIndex < 0 || candidateIndex < openingIndex)) {
      openingIndex = candidateIndex;
    }
  }
  // No check for inline code context
  // ...
  return openingIndex;
}

Explanation

findEnrichmentStart uses a simple indexOf search for {{kind: tokens anywhere in the source string. It does not detect whether the match occurs inside an inline code span (backticks). As a result, when the input contains `{{hint:test}}`, the frontend begins enrichment processing mid-code-span and converts the content into an enrichment card, leaving surrounding backticks in the rendered HTML.

Codebase Inconsistency

Backend explicitly verifies (and documents) the expected behavior: inline code markers must not be transformed into enrichment cards.

src/test/java/.../MarkdownServiceTest.java

@Test
@DisplayName("Inline code markers are not transformed into enrichment cards")
void testInlineCodeMarkerNotRendered() {
    String markdown = "Inline marker: `{{hint: no-card}}` should stay code.";
    String html = markdownService.processStructured(markdown).html();
    assertFalse(html.contains("inline-enrichment"));
    assertTrue(html.contains("<code>{{hint: no-card}}</code>"));
}

Failing Test

frontend/src/lib/services/inline-code-bug.test.ts

import { parseMarkdown } from './markdown';
import { describe, it, expect } from 'vitest';

describe('Inline code enrichment bug', () => {
  it('should NOT render enrichment markers inside inline code (backend parity)', () => {
    const markdown = 'The pattern `{{hint:test}}` should not render as enrichment';
    const html = parseMarkdown(markdown);
    expect(html).not.toContain('data-enrichment-type');
    expect(html).toContain('<code>{{hint:test}}</code>');
  });
  
  it('should NOT render enrichment markers inside inline code with space after colon', () => {
    const markdown = 'Inline marker: `{{hint: no-card}}` should stay code.';
    const html = parseMarkdown(markdown);
    expect(html).not.toContain('inline-enrichment');
    expect(html).toContain('<code>{{hint: no-card}}</code>');
  });
});

Observed failure (against HEAD) includes an enrichment card in the output:

<div class="inline-enrichment hint" data-enrichment-type="hint"> ...

Recommended Fix

In findEnrichmentStart, add an inline-code context check before returning openingIndex (e.g., by scanning the substring before the match for unmatched backtick runs). If the match is inside inline code, return -1 (or continue searching for a later match outside inline code).

History

This bug was introduced in commit f72e0cba. The commit added enrichment marker support to the frontend markdown parser with a simple src.indexOf('{{') approach in the start function, lacking any code context awareness. The backend had already added inline code protection in commit 1590a988 (earlier the same day), but the frontend implementation never received equivalent protection. Subsequent commits (53bd3caa, fb67d01c) added fence-aware scanning but continued to miss inline code spans.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions