Plain-text fallback principle

Rho's core promise: any .md file you write opens in any tool — worst case, the reader sees syntax-highlighted source, not a failed render or broken binary. This principle constrains every AINP block's design — and is the fundamental difference between AINP and MDX / Notion-flavored markdown / other "enhanced markdown" formats.


Markdown's core promise

Why did markdown beat wiki / RTF / docx / Notion's proprietary formats and become the de facto standard?

Any tool can open it. Worst case you see plain text — not a failed render or broken binary.

That promise lets .md files flow across:

  • GitHub / GitLab / Bitbucket
  • VS Code / Sublime / IntelliJ
  • Obsidian / Bear / Typora
  • Email clients (Apple Mail / Gmail / Outlook)
  • Command line (cat / less / vim)
  • Any text editor (including Notepad)

Readers never can't open your file because they switched tools.


How Rho honors it

All AINP blocks must look like legal markdown — in non-Rho readers they show as:

  • Standard fenced code blocks (```-wrapped)
  • Standard directive containers (:::-wrapped)
  • Standard blockquotes (>-prefixed)

Per-DSL fallback:

AINP block In a non-supporting reader
Callout (> [!INFO]) Standard blockquote + [!INFO] literal visible (GitHub natively renders some types as GFM alerts)
Layout (```layout) Code block + :::card literal + card markdown renders normally
Tabs (```tabs) Code block + :::tab literal + tab content inlined
Stepper (```stepper) Code block + :::step literal + steps inlined sequentially
Modal (```modal) Code block (trigger + body all visible — no "hidden" effect)
Timeline (```timeline) Code block + :::event literal + events inlined sequentially
Annotate (```annotate) Code block (body + annotation table)
Interact (```interact) Code block (slider/template literals)
STL syntax (```stl) Code block (no 6-color highlighting; plain text readable)

Key observation: every DSL lives inside markdown-already-legal containers — code blocks and directive containers are CommonMark / GFM legal syntax; they just aren't recognized as Rho-specific semantics elsewhere.


Comparison with other "enhanced markdown"

Format Breaks plain-text fallback? How
MDX ❌ Breaks .md contains JSX <Component />; outside an MDX reader → file is completely broken (won't render + isn't legal markdown)
Notion-flavored markdown ❌ Breaks Notion's custom blocks (embed / database / synced) export to .md that's unreadable elsewhere
Bear-flavored ❌ Breaks Bear's custom tag / link syntax shows as gibberish elsewhere
MyST ⚠️ Partly MyST uses :::{directive} similar to Rho; plain markdown sees them as literals; but MyST is complex with broader fallback challenges
Quarto ⚠️ Partly Quarto's {r} / {python} chunks fall back as code-block literals (OK), but inline {=ref} etc. show as gibberish in plain markdown
GitHub-flavored markdown (GFM) ✅ Doesn't break task lists / tables / strikethrough all have reasonable fallback
Rho ✅ Doesn't break All DSLs live in fenced code / directive containers; always plain-text readable

Cost of Rho's design: DSL shape is constrained by markdown grammar (must live in ``` or :::). Benefit: your .md files never break because you switched readers.

MDX takes the opposite path: embrace React ecosystem, abandon plain-text fallback. That's a reasonable engineering choice, but Rho doesn't accept that trade-off — Rho aims to be markdown's next evolution, not React's markdown adapter.


How to verify your doc actually fallbacks plain-text

After writing a Rho doc, test in at least one fallback reader:

1. Push to GitHub

GitHub is the strictest fallback test environment — it doesn't render any non-standard markdown extensions.

git add my-doc.md
git commit -m "test"
git push

Open my-doc.md on GitHub, check:

  • ✅ Standard markdown (headings / lists / code blocks / tables) renders normally
  • ✅ Callout [!NOTE] / [!TIP] / [!IMPORTANT] / [!WARNING] / [!CAUTION] renders (GFM alerts)
  • ⚠️ Callout [!INFO] / [!WARN] / [!ZEN] shows as [!INFO] literal + plain blockquote
  • ⚠️ :::card :::tab :::step :::event show as literals + content normal
  • ⚠️ ```layout ```interact ```timeline show as code blocks (with full source)

Bottom line: all content readable, no breakage (no 404 / gibberish / chaos).

2. VS Code default preview

Open .md → Cmd+Shift+V (default markdown preview):

  • Standard markdown renders
  • Rho-specific syntax shows as literals

3. Command-line cat

cat my-doc.md

This is the most extreme fallback — readers see only raw plain text. Every Rho source should be readable — sliders are spelled out, templates show their placeholders.


How to actively maintain fallback while writing

1. Don't use any "non-standard markdown" outside AINP blocks

<custom-element>This isn't legal markdown</custom-element>     ← ❌ Risky to use HTML outside Rho

Use extension syntax only inside AINP blocks. Regular paragraphs stay 100% CommonMark / GFM legal.

2. interact templates clearly self-explain

```interact
slider weight range=40..120 step=1 default=70 label="weight"
template stl:
[Weight] -> [{weight} kg]      ← ✅ Template self-explanatory
```

→ Plain-text readers seeing slider weight range=40..120 step=1 default=70 label="weight" + [Weight] -> [{weight} kg], can guess "drag a slider, change weight".

Don't use mysterious abbreviations / custom syntax that confuses plain-text readers.

3. Don't hide critical content in modal

```modal trigger="Details"
(Critical explanation)
```

→ Plain-text readers see \```modal trigger="Details" + content fully expanded — no "hide" effect. So hiding critical content in a modal = not actually hidden in plain text = you think you hid it but didn't, while in Rho you hid it from users.

Conclusion: modals only hide "optional depth."

4. annotate's positions / colors must be self-explained in the table

```annotate
The quick brown fox
---
4-9|red|adjective
10-15|blue|noun
```

→ Plain-text readers see positions + colors + descriptions and can manually count to verify, understanding the original. Don't use unlabeled annotations — loses fallback value.

5. STL nodes use semantic names

```stl
[A] -> [B] ::mod(rule="causal")     ← ⚠️ "A" / "B" too abstract
[Rate_Increase] -> [Inflation_Rise] ::mod(rule="causal")     ← ✅ Semantic names
```

→ Plain-text readers see semantic names and immediately understand what nodes mean; A/B/X/Y are just letters in plain text.


When violating this principle is acceptable

Almost never. But two exceptions:

1. Internal tools / personal scripts generating .md

If the file only flows within your Rho-only pipeline (will never be opened on GitHub by anyone), you can relax — but write down why you abandoned fallback for future traceability.

2. Embedding raw HTML / Vue / React in Rho

Some integrating readers may extend support. Such extensions work only in that reader; outside, broken. Rho's official spec doesn't encourage this — if you do it, isolate in a dedicated plugin / section, don't mix into general docs.


Common pitfalls

1. Using HTML tags in place of an AINP block

<details>
<summary>Click to expand</summary>
content
</details>

→ HTML <details> renders foldable on GitHub / Obsidian (legal), but:

  • Can't combine with Rho interact / computed / etc.
  • Inconsistent with Rho's brand
  • Loses "pure markdown" advantage

Use Rho's Modal instead for consistency.

2. Raw HTML for layout

<div class="grid">
  <div>Left</div>
  <div>Right</div>
</div>

→ GitHub doesn't render <div> CSS (GitHub strips style). Use Rho's Layout.

3. Emoji as semantic signals — but plain-text loses structure

🟢 Recommended
🔴 Forbidden
🟡 Caution

→ Plain-text readers see only emoji chars, no visual weight. Use Callout:

> [!INFO]
> Recommended

> [!CAUTION]
> Forbidden

> [!WARN]
> Caution

Callouts in fallback are blockquote + [!TYPE] literal — structural information preserved.

4. Thinking plain-text fallback is a "security mechanism"

❌ Misconception: I can hide sensitive data in modal / interact since non-Rho users can't see it. ✅ Reality: every AINP block in fallback shows all content as plain text. Rho is a UX tool, not a security tool — sensitive data should not enter markdown at all.

5. Nesting destroys plain-text readability

```layout grid cols=4
:::card
```layout grid cols=3
...5 levels deep...

→ Even with valid syntax, plain-text readers see 30 ::: and ``` markers and can't understand structure. Keep ≤ 3 levels of nesting, with each level's responsibility clear. See Nested DSL.

6. interact templates with mysterious expressions

template stl:
[Result] -> [{round((p*pow(1+r,n)-p)/(p*r), 4)}]     ← ❌ Plain-text reader has no clue

→ Use computed to break it apart and name intermediates:

computed gain = principal * pow(1+rate, years) - principal
computed roi = gain / (principal * rate)
template stl:
[ROI] -> [{round(roi, 4)}]

Plain-text readers see computed gain = ... + computed roi = ... + template ... [{round(roi, 4)}] and can follow the logic.

7. Assuming the reader "definitely supports" Rho

Drag the slider below to see the result:

```interact
slider x range=0..10 step=1 default=5 label="x"
template stl: [{x}]
```

→ If the reader views on GitHub, there's nothing to drag. Rewrite to be plain-text-friendly:

Below is an interactive slider (draggable in Rho / rho.md; shown as code elsewhere):

```interact
slider x range=0..10 step=1 default=5 label="x"
template stl: [{x}]
```

Let plain-text readers also understand what's happening.


Philosophy: why this principle can't bend

Markdown is markdown not because it has fancy capabilities — other formats are more capable. Markdown's victory rests entirely on simple + universal + indestructible.

Breaking plain-text fallback = breaking the root that makes markdown markdown.

Rho is markdown's next evolution — evolution, not revolution. We extend capabilities but preserve the root. This principle never bends.

"If users' files break because they switched tools, you've destroyed all of markdown's value."


See also

  • What is Rho? — One of Rho's three core principles is this one
  • Nested DSL — Nesting still falls back
  • Shared state — namespace also falls back (namespace literal visible)
  • The "Plain-text fallback behavior" sections in every capability doc
Open in Rho MD →