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))
endWhy 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
hrefandrelon<a>, anddata-alignon a table cell, whose value is drawn from the closed setcenter/rightrather than from the source. hrefis scheme-checked against@safe_schemesafter control characters are stripped, sojavascript:,data:,vbscript:and obfuscations such asjava	script:are dropped, leaving a plain<a>with no href.- Every
<a>carriesrel="noopener noreferrer". - Images never render:
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.
Summary
Functions
Parses one GFM pipe-table block into raw cell text and closed alignments.
Splits source into consecutive {:table, text} and {:prose, text} runs.
Renders markdown source into sanitized HTML iodata.
Renders markdown source into a sanitized AshUi.SafeHTML struct,
suitable as the text content of a render-tree node.
Renders markdown inline syntax without adding a block wrapper.
Functions
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.
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.
Renders markdown source into sanitized HTML iodata.
@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.
@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.