Task: Refactor Markdown Component and Unify Rendering with MDXEditor

Refactor Markdown Component and Unify Rendering with MDXEditor

09.09.2026haih agent

Resolve incompatibilities between react-markdown for reading and @mdxeditor/editor for editing: a single Markdown content must be interpreted identically in the editor, client-side renderer, and SSR.

Context

Currently in haih-agent, the same content format—Markdown—is effectively handled by two different engines:

  • react-markdown is used for reading/rendering;
  • @mdxeditor/editor is used for editing.

At the product level, this looks like a single format, but internally it uses different parsers, different AST/serialization pipelines, and different syntax assumptions. As a result, Markdown that passes normally through the editor and is saved by it may be interpreted differently during public rendering.

The issue has already surfaced on VietnamGuru and is now recurring on BiznesHelper, forcing us to write local custom Markdown components instead of a core-level solution.

Key Limitation: SSR

For the public website, Markdown must be rendered correctly on the server. You cannot simply use MDXEditor as a single reading runtime: the editor is built on top of a client-side rich-text/Lexical stack and is not a replacement for a lightweight SSR-safe Markdown renderer.

Therefore, the task is not limited to "using one component everywhere." We need to achieve identical Markdown semantics across different runtimes while preserving SSR.

Confirmed Conflict: Indentation

One of the most tricky cases involves spaces and indentation.

When importing/exporting Markdown, MDXEditor may handle extra indentation gracefully and serialize content with indentation itself. However, a CommonMark-compliant renderer treats four leading spaces or tab stops as indented code block syntax. Consequently, a visually and semantically correct fragment with HTML/nested tags can turn into <pre><code> instead of HTML/content after being saved by the editor.

This is a standard CommonMark rule: four spaces represent indented code block syntax. This means the problem is not a random bug on a single page—it is a systemic incompatibility between the editor's and the renderer's round-trip behavior.

Why This is Critical

Content in haih-agent is increasingly AI-driven and automatically modified by editors/agents. If a Markdown string does not have a stable interpretation across the edit/read/SSR pipeline, any automatic rewrite can imperceptibly change the document structure.

This is especially dangerous for:

  • HTML inside Markdown;
  • custom components/directives;
  • nested blocks;
  • tabs and indentation;
  • lists;
  • fenced/indented code blocks;
  • AI-generated formatting;
  • round-trip editor -> markdown -> renderer.

Goal

Create a single predictable Markdown contract for the entire platform: content saved by MDXEditor or an AI agent must be displayed identically and safely via the SSR/read renderer without needing to write project-specific Markdown components.

What to Investigate

  • The exact parser/serializer stack of @mdxeditor/editor: mdast visitors, HTML nodes, directives, JSX/MDX, and whitespace serialization rules.
  • The exact react-markdown/remark pipeline in the current core and its CommonMark/GFM settings.
  • Which differences are plugin configurations versus fundamental differences between the two implementations.
  • Behavior of HTML blocks, inline HTML, and nested Markdown inside HTML.
  • Behavior of 4-space indentation and tabs after a round-trip through MDXEditor.
  • Lists/blockquote/code fence edge cases.
  • Custom tags/components already used in haih projects.
  • Whether a shared remark/mdast normalization pipeline can be extracted and applied both before saving and before SSR rendering.
  • Whether Markdown canonicalization/normalization is needed before writing to the database.
  • Whether to prohibit indented code blocks at the platform level and use only fenced code blocks to eliminate ambiguity with indentation in our content subset.
  • The possibility of separating the Markdown source contract from specific editor/renderer UI components.

Preferred Direction

We should think not in terms of two React components, but a single content specification:

Markdown source → single normalization/validation layer → MDXEditor for editing → SSR-safe renderer for reading

Both ends should operate on top of a single agreed-upon Markdown subset and identical mdast/remark rules as much as possible.

If MDXEditor generates syntax during serialization that the read renderer interprets differently, this must be fixed in the shared serializer/normalizer, not in each project individually.

Possible Technical Directions

1. Unified Markdown Normalization Pipeline

Before saving or rendering, run the document through a shared mdast pipeline that:

  • normalizes problematic indentation;
  • preserves actual fenced code blocks;
  • does not turn nested HTML into indented code;
  • brings tabs/whitespace to a canonical form;
  • validates custom nodes/directives.

2. Shared Set of Remark/Rehype Plugins

Reduce parser options and extensions to the most general set possible. All platform-specific Markdown extensions must be registered centrally so that both the editor and renderer understand the same set of capabilities.

3. Explicit haih-agent Markdown Dialect

Lock down the supported syntax subset. For example, if indented code blocks are not needed by the product, we can canonically use only fenced code blocks and safely normalize ambiguous four spaces in regular content. This must be a deliberate platform decision, not a random regex fix.

4. Round-Trip Test Suite

Create a corpus of complex Markdown examples and verify:

source -> MDXEditor -> exported markdown -> SSR renderer

The semantic result must be preserved.

Mandatory Test Cases

  • HTML block with nested lines and four spaces.
  • Tabs inside HTML/markdown blocks.
  • Real indented code block.
  • Fenced code block.
  • Nested lists with four or more spaces.
  • Blockquote + list + code.
  • Custom HTML tags.
  • Custom haih-agent directives/components.
  • Markdown after MDXEditor saving without manual changes.
  • AI-generated Markdown with formatting.
  • SSR render and client hydration of a single document.

Result

There is a single platform Markdown component/contract in the core that can be used across all projects without local forks. The editor and read renderer may remain technically different libraries, but for supported content, they yield a compatible result.

Definition of Done

  • The same saved Markdown displays correctly after an MDXEditor round-trip and through the SSR renderer.
  • The scenario with four spaces/tabs and HTML no longer turns valid content into a code listing.
  • SSR is preserved and does not require a browser-only editor runtime.
  • There is a set of regression tests for known incompatibilities.
  • VietnamGuru and BiznesHelper projects can remove their project-specific Markdown compatibility components.
  • New Markdown extensions are added via a single platform-level mechanism.
  • Behavior is documented as the haih-agent Markdown contract.

Reference

CommonMark treats four leading spaces as an indented code block, so this behavior of react-markdown cannot be considered a random renderer bug. MDXEditor, for its part, imports and exports Markdown via a separate set of mdast/Lexical visitors. Therefore, a robust solution must reside at the level of a coordinated specification/normalization pipeline, rather than targeted CSS or UI fixes.