Skip to content

✨ Render arbitrary text safely with <CodeBlock> #658

Description

@taras

Story

As an Executable Markdown author, I want to render an arbitrary string as a fenced Markdown code block without that string being able to close the fence, so generated source and diagnostics can be presented safely without eval.

<CodeBlock value={source} language="markdown" />

Contract

<CodeBlock> is a generic first-party text component.

  • It is self-closing and requires a string value.
  • language is optional. When present, it is non-empty and matches ^[A-Za-z0-9][A-Za-z0-9._+#-]*$. This admits common single-token language identifiers such as markdown, shell-session, c++, and c#, while excluding whitespace, backticks, and Markdown extension syntax outside that closed alphabet.
  • A backtick run is a contiguous sequence of U+0060 characters in value. The component chooses the shortest safe fence: max(3, longest backtick run + 1) backticks.
  • The rendered string is exactly fence + (language ?? "") + "\n" + value + "\n" + fence. The two U+000A line feeds frame the value and are not part of it. The component adds no line feed after the closing fence.
  • The value's characters appear unchanged between the framing line feeds. It is not escaped, parsed, evaluated, normalized, or rewritten.
  • Exactness is defined at the component rendering and as capture boundary. Ordinary DocumentOutput middleware remains free to transform emitted document output under its existing contract; a caller requiring terminal byte preservation uses xmd run --raw.
  • It performs no durable effect and uses ordinary text rendering and as capture semantics. Capturing with as receives the exact rendered string above and emits nothing at the invocation site.
  • It is an ordinary overridable core default. A repository CodeBlock.md is selected ahead of it under normal component resolution.
  • Content, a missing value, a non-string value, and an invalid language token are refused before rendering.
  • A candidate containing fences, component syntax, interpolation, HTML, or executable blocks remains inert text.

Suggested syntax description:

Show arbitrary text as a fenced Markdown code block. <CodeBlock value={source} language="markdown" /> chooses a fence the value cannot close.

Motivation

Issue #260 needs to show the exact generated document and structured validation diagnostics inside an <Elicit> message. Computing a delimiter in an eval block would move generic presentation machinery into authored JavaScript and would give the planning document authority it does not need.

Acceptance

  • Empty text and ordinary Markdown render as one valid fenced block using the shortest safe fence.
  • A value containing backtick runs longer than three receives a fence exactly one character longer than its longest run and cannot close it.
  • Removing the opening delimiter and its framing line feed, and the closing delimiter and its framing line feed, recovers the exact input string, including leading and trailing whitespace and repeated blank lines.
  • An omitted language produces no info string. language="md", language="json", language="shell-session", language="c++", and language="c#" produce those exact tokens.
  • Empty language, whitespace, backticks, and characters outside the closed language alphabet are refused.
  • Invalid shape or props render nothing and perform no component effect.
  • Capturing with as returns the exact fenced Markdown the raw uncaptured component rendering produces. Normalized host output and --raw behavior retain their existing contracts.
  • The component has no component-specific durable event. A partial replay renders from the reconstructed value, and completed-root reuse does not invoke it.
  • A repository CodeBlock.md overrides the core default; otherwise inspection reports the ordinary @executablemd/core registration with self-closing form, the closed props schema, text return mode, and ordinary optional as behavior.
  • The component is documented in the syntax catalog and covered under Deno, Node and Bun through the shared corpus.

Relationship

This is a prerequisite for #260. It is presentation only: it does not admit or execute the supplied text.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions