Skip to main content

Markdown Capabilities

Markdown Capabilities Overview

Two independent markdown pipelines exist in AFS UI with different capability sets — this page explains the split so authors don't write pipeline-B-only syntax into a pipeline-A field.

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 APipeline B
Where it livesAUP text primitive, format: "markdown"web-device content pipeline (this page)
Implementationweb-page/core.ts + renderers/text.tscontent-reader.ts
Enginemarked@15 + highlight.js + DOMPurify (CDN, lazy-loaded + SRI)remark-parse + remark-gfm + remark-directive
Authoring surfaceA 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)YesYes
Code highlightingYes (highlight.js)Yes (language label + copy button)
Relative links → AUP navigation eventsYesNo — this pipeline renders real anchor tags for a real URL-routed site
Admonitions (> [!NOTE] etc.)NoYes — see Admonitions
Directives (::name{...}, :::name ... :::)NoYes — cards, steps, tabs, embeds all use this
Footnotes ([^1])NoYes — see GFM extras
link-card / action-card / info-card / card-groupNoYes — see Cards
steps tutorial containerNoYes — see Steps
Tabs (view{mode="tabs"})NoYes — see Tabs
Embeds (YouTube / Twitter / bare URL)NoYes — 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:

ExtensionFiles using it
link-card768
card-group741
steps / step362
embed283
action-card176
GFM alert (> [!...])69
Footnotes ([^n])8
info-card0 (this reference is its first real consumer)
Tabs0 (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.

  • --- (a thematicBreak / horizontal rule on its own line) is silently dropped. content-reader.ts's mdastBlockToAup() has an explicit case "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 a zh table; a bare [!TIP] marker with no locale entry falls back to the hardcoded English label. This showcase site runs a single en locale, 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 default min-width: auto can 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 via window.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
  • Cardslink-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