AFS UI has two independent markdown pipelines. They look similar (both start from markdown text) but support a genuinely different syntax set, and mixing them up is the single most common way a public site author writes something that silently fails to render.
Pipeline A vs pipeline B
| Pipeline A | Pipeline B | |
|---|---|---|
| Where it lives | AUP text primitive, format: "markdown" | web-device content pipeline (this page) |
| Implementation | web-page/core.ts + renderers/text.ts | content-reader.ts |
| Engine | marked@15 + highlight.js + DOMPurify (CDN, lazy-loaded + SRI) | remark-parse + remark-gfm + remark-directive |
| Authoring surface | A string prop inside an AUP page JSON node (.aup/pages/*.json) | A content.md file with YAML front matter, read straight off AFS |
| GFM (tables, task lists, strikethrough) | Yes | Yes |
| Code highlighting | Yes (highlight.js) | Yes (language label + copy button) |
| Relative links → AUP navigation events | Yes | No — this pipeline renders real anchor tags for a real URL-routed site |
Admonitions (> [!NOTE] etc.) | No | Yes — see Admonitions |
Directives (::name{...}, :::name ... :::) | No | Yes — cards, steps, tabs, embeds all use this |
Footnotes ([^1]) | No | Yes — see GFM extras |
link-card / action-card / info-card / card-group | No | Yes — see Cards |
steps tutorial container | No | Yes — see Steps |
Tabs (view{mode="tabs"}) | No | Yes — see Tabs |
| Embeds (YouTube / Twitter / bare URL) | No | Yes — see Embeds |
The practical consequence: if you're editing an AUP page JSON (.aup/pages/*.json)
and your field is a text node with format: "markdown", none of admonitions,
directives, footnotes, link-card/action-card/info-card/card-group, steps,
tabs, or embeds will render. There's no error — the directive/admonition syntax is
either left as literal text or silently dropped, depending on what marked's parser
makes of it. If you're writing a content.md file for a site (like this one), all of
the above work.
Pipeline A's own reference page is the AUP Showcase gallery's Markdown card
(.aup/pages/markdown.json, reached by clicking through the gallery — that page is
an in-app SPA route, not a URL, so it isn't hyperlinked from here). That page
carries a matching one-line caveat pointing back to this reference.
What pipeline B covers, by real-world usage
A scan of a large production markdown corpus (6,295 files) found these extensions in active use, ranked by file count:
| Extension | Files using it |
|---|---|
link-card | 768 |
card-group | 741 |
steps / step | 362 |
embed | 283 |
action-card | 176 |
GFM alert (> [!...]) | 69 |
Footnotes ([^n]) | 8 |
info-card | 0 (this reference is its first real consumer) |
| Tabs | 0 (this reference is its first real, visible consumer) |
The pages in this reference are ordered to match that usage — cards first, then steps, then the less-used-but-fully-supported tabs and embeds.
Known gaps — stated honestly
This is a public reference; an unfinished capability gets a status marker instead of being quietly omitted.
---(athematicBreak/ horizontal rule on its own line) is silently dropped.content-reader.ts'smdastBlockToAup()has an explicitcase "thematicBreak": return null— no<hr>is ever emitted. If you write a bare---between sections expecting a divider, nothing will appear and no warning is logged. (Not to be confused with the front-matter---fence, which works correctly — this is a---line inside the document body.)- Per-locale admonition labels (
ADMONITION_LOCALIZED_LABELS) currently only have azhtable; a bare[!TIP]marker with no locale entry falls back to the hardcoded English label. This showcase site runs a singleenlocale, so that behavior isn't visible as a live example here — see the Admonitions page for the documented behavior. - Avoid a long, unbroken inline
`code span`inside a heading. Headings (.aup-text[data-level]) are laid out as a flex row (for the leading accent bar); a flex child's defaultmin-width: autocan let a long unbroken code token push the heading past its column at narrow widths even though the same token wraps fine inside a paragraph. Verified against this exact pipeline while building this reference (a heading titled with a full`::embed{url="..."}`snippet produced a real, if small, page-wide horizontal scroll at 390px — confirmed viawindow.scrollTo, not just a CSS-rule check). The fix here was simply to keep that heading's title in plain words and show the snippet in the body instead; this page's own headings follow that rule now.
Reading the source instead of the render
Every page in this reference is a plain content.md file, readable via AFS at:
/packages/content/docs/markdown/<slug>/content.md(the blocklet's own source tree is projected onto its runtime AFS at
/packages — see runtimes/node/src/program/man-action.ts's MAN_DIR for
the same convention; the repo-relative path is blocklets/showcase/content/
docs/markdown/<slug>/content.md, but that's not what an agent reads through
AFS at runtime.)
No rendering required. An agent that needs to know "what markdown syntax
produces a two-column card group" can read that file directly instead of
parsing this page's HTML. See .aup/man/docs-markdown.yaml for the
machine-readable pointer.
Pages in this reference
- GFM extras — tables, task lists, footnotes, strikethrough, code highlighting
- Admonitions — five alert types, custom titles, locale defaults
- Cards —
link-card,action-card,info-card,card-group - Steps — the tutorial container, including the colon-nesting rule
- Tabs — 2 tabs, 8+ tabs, long CJK labels, nested code
- Embeds — YouTube, Twitter/X, bare-URL auto-detection