# `AshUi.Markdown`
[🔗](https://github.com/hyperbob-labs/ash_ui/blob/main/lib/ash_ui/markdown.ex#L1)

Markdown source -> sanitized `AshUi.SafeHTML`.

This is the single shared markdown seam for AshUi views. It lives beside
`AshUi.SafeHTML` because SafeHTML is the only sanctioned way to put
non-escaped markup into a render tree: the constructor vocabulary is closed
(`AshUi.ViewConstructor`), so a view keeps emitting an ordinary
`text(...)` node and instead changes what that expression *evaluates to*.

Callers build the value where the row is built (a calculation), never in the
view:

    calculate :body_html, :term, fn records, _context ->
      Enum.map(records, &AshUi.Markdown.to_safe_html(&1.plain_text))
    end

## Why this is self-contained

Rendering is done here rather than through a markdown library because adding
any dependency to this package forces Mix to re-resolve and bump unrelated
transitive pins. The supported syntax is deliberately a small, chat-shaped
subset, which is tractable to render directly and keeps the package
dependency-free.

Supported: paragraphs, `#`..`######` headings, `**bold**`/`__bold__`,
`*italic*`/`_italic_`, `~~strikethrough~~`, `` `code spans` ``, fenced code
blocks, unordered (`-`/`*`/`+`) and ordered (`1.`) lists, `>` blockquotes,
thematic breaks, GFM pipe tables, and `[text](href)` links. Single newlines
inside a paragraph become `<br>`, which matches how chat bodies are written.

A table is recognised only when a header row is followed immediately by a
delimiter row (`|---|:-:|--:|`), so ordinary prose containing a `|` is never
mistaken for one. Rows are padded or truncated to the header's column count,
so a ragged row cannot shift later cells into the wrong column.

## Sanitization

`SafeHTML` means "emit these bytes verbatim" and performs no escaping of its
own, so everything here is whitelisted by construction rather than filtered
after the fact. Message bodies are user input and assistant replies are model
output; neither is trusted.

  * Only the tags this module writes are ever emitted -- there is no path from
    source text to an arbitrary tag name.
  * Every run of text is escaped through
    `AshUi.RenderTarget.Html.html_escape/1`, so raw HTML in the source
    is inert: a user who types `<script>alert(1)</script>` sees that text.
  * The only attributes ever emitted are `href` and `rel` on `<a>`, and
    `data-align` on a table cell, whose value is drawn from the closed set
    `center`/`right` rather than from the source.
  * `href` is scheme-checked against `@safe_schemes` after control characters
    are stripped, so `javascript:`, `data:`, `vbscript:` and obfuscations such
    as `java&#9;script:` are dropped, leaving a plain `<a>` with no href.
  * Every `<a>` carries `rel="noopener noreferrer"`.
  * Images never render: `![alt](src)` degrades to its alt text.

No `class` or `style` attribute is ever emitted -- markdown output is painted
by descendant-element CSS under the owning `[part=...]` node, matching the
existing `[part="agent_markdown"] code` rule.

# `parse_table`

```elixir
@spec parse_table(term()) ::
  {:ok, %{headers: [map()], rows: [[map()]]}} | {:error, :not_a_table}
```

Parses one GFM pipe-table block into raw cell text and closed alignments.

Callers can then render the table with ordinary AshUi table/cell nodes,
preserving interactive content inside cells without reimplementing the
markdown table grammar.

# `split_tables`

```elixir
@spec split_tables(term()) :: [{:table | :prose, binary()}]
```

Splits `source` into consecutive `{:table, text}` and `{:prose, text}` runs.

Table syntax is known only to this module, so callers that must treat a table
as an indivisible unit ask here rather than re-deriving the grammar. Structural
consumers can then pass each table block to `parse_table/1` and keep
interactive children inside their owning cells.

Concatenating the returned texts reproduces `source` exactly.

# `to_iodata`

```elixir
@spec to_iodata(term()) :: iodata()
```

Renders markdown `source` into sanitized HTML iodata.

# `to_safe_html`

```elixir
@spec to_safe_html(term()) :: AshUi.SafeHTML.t()
```

Renders markdown `source` into a sanitized `AshUi.SafeHTML` struct,
suitable as the text content of a render-tree node.

# `to_safe_inline_html`

```elixir
@spec to_safe_inline_html(term()) :: AshUi.SafeHTML.t()
```

Renders markdown inline syntax without adding a block wrapper.

This is intended for structural consumers such as table cells that still
need AshUi-owned interactive children between formatted text runs.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
