Plain-text fallback principle

Rho 的核心承诺:你写的 .md 文件任何工具都能打开——最坏情况下读者看到带语法高亮的源码,不是渲染失败的白屏 / 损坏的二进制。 这条原则约束了所有 Rho DSL 的设计——也是 Rho 区别于 MDX / Notion-flavored / 其他"增强版 markdown"的根本。


Markdown 的核心承诺

为什么 markdown 战胜了 wiki / RTF / docx / Notion 私有格式成为事实标准?

任何工具都能打开它。最坏情况下你看到的是纯文本——而不是渲染失败的白屏 / 损坏的二进制。

这条承诺让 .md 文件可以横跨:

  • GitHub / GitLab / Bitbucket
  • VS Code / Sublime / IntelliJ
  • Obsidian / Bear / Typora
  • 邮件客户端(Apple Mail / Gmail / Outlook)
  • 命令行 (cat / less / vim)
  • 任何文本编辑器(包括 Notepad)

读者永远不会因为换工具而打不开你的文件。


Rho 怎么遵守这条

Rho 的所有 DSL 必须 看起来像合法的 markdown——在不支持 Rho 的 reader 里显示为:

  • 普通 fenced code block(``` 包围)
  • 普通 directive container(::: 包围)
  • 普通 blockquote(> 包围)

每种降级行为:

Rho DSL 在不支持 reader 里
Callout (> [!INFO]) 普通 blockquote + [!INFO] 字面量可见(GitHub 自带 GFM alert 渲染部分类型)
Layout (```layout) 代码块 + :::card 字面量 + 卡内 markdown 正常渲染
Tabs (```tabs) 代码块 + :::tab 字面量 + tab 内容 inline 展开
Stepper (```stepper) 代码块 + :::step 字面量 + 步骤顺序展开
Modal (```modal) 代码块(trigger + body 全显示,无"隐藏"效果)
Timeline (```timeline) 代码块 + :::event 字面量 + 事件顺序展开
Annotate (```annotate) 代码块(含正文 + 标注表)
Interact (```interact) 代码块(slider/template 字面量)
STL syntax (```stl) 代码块(无 6 色高亮,纯文本可读)

关键观察:所有 DSL 都装在 markdown 语法已合法的容器里——code block 和 directive container 是 CommonMark / GFM 约定的合法语法,只是不被识别成 Rho 特殊语义。


跟其他 "增强版 markdown" 对比

格式 是否破坏 plain-text fallback 怎么破坏
MDX ❌ 破坏 .md 含 JSX <Component />,离开 MDX reader → 文件完全坏掉(render 不出来 + 不是合法 markdown)
Notion-flavored markdown ❌ 破坏 Notion 自定义 block(embed / database / synced)export 出来的 .md 在别处不可读
Bear-flavored ❌ 破坏 Bear 自定义 tag / link 语法在别处显示为乱码
MyST ⚠️ 部分破坏 MyST 用 :::{directive} 跟 Rho 类似,普通 markdown 视为字面量;但 MyST 复杂度高,更难全程 fallback
Quarto ⚠️ 部分破坏 Quarto 的 {r} / {python} chunks 在非 Quarto reader 显示为代码块字面量(fallback OK),但 inline {=ref} 等在普通 markdown 显示为乱码
GitHub-flavored markdown (GFM) ✅ 不破坏 task list / table / strikethrough 在普通 markdown 都有合理 fallback
Rho ✅ 不破坏 所有 DSL 都装在 fenced code / directive container 里,永远 plain-text 可读

Rho 设计的代价:DSL 形式受 markdown 语法限制(必须装在 ``` 或 ::: 里)。 Rho 设计的收益:你的 .md 文件永远不会因为换 reader 而坏。

MDX 走另一条路:拥抱 React 生态、放弃 plain-text fallback。这是合理的工程选择,但 Rho 不接受这个 trade-off——Rho 的目标是 markdown 的 next evolution,不是 React 的 markdown 适配。


怎么验证你的文档真的 plain-text fallback

写完 Rho 文档后,至少测试一种 fallback reader:

1. 推到 GitHub 仓库

GitHub 是测试 fallback 最严格的环境——它不渲染任何非标准 markdown 扩展。

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

打开 GitHub 上的 my-doc.md,看:

  • ✅ 标准 markdown(标题 / 列表 / 代码块 / 表格)正常
  • ✅ Callout [!NOTE] / [!TIP] / [!IMPORTANT] / [!WARNING] / [!CAUTION] 正常(GFM alert)
  • ⚠️ Callout [!INFO] / [!WARN] / [!ZEN] 显示为 [!INFO] 字面量 + 普通 blockquote
  • ⚠️ :::card :::tab :::step :::event 显示为字面量 + 内容正常
  • ⚠️ ```layout ```interact ```timeline 显示为代码块(含全部源)

底线:所有内容可读,无破坏性(无 404 / 乱码 / 错乱)。

2. VS Code 默认预览

打开 .md 文件 → Cmd+Shift+V(默认 markdown preview):

  • 标准 markdown 渲染
  • Rho 特殊语法显示为字面量

3. 命令行 cat

cat my-doc.md

这是最极端的 fallback——读者只看到原文 plain text。所有 Rho 源应该可以阅读——sliders 写得明明白白,template 看得见占位符。


写作时怎么主动维护 fallback

1. 不要在 Rho DSL 外用任何"非标准 markdown"

<custom-element>这不是合法 markdown</custom-element>     ← ❌ 在 Rho 外用 HTML 风险

只在 Rho DSL 里用扩展语法。普通段落保持 100% CommonMark / GFM 合法。

2. interact 的 template 写法明确

\```interact
slider weight 40 120 70 1
template stl:
[体重] -> [{weight} kg]      ← ✅ template 自我解释
\```

→ Plain-text reader 看到 slider weight 40 120 70 1 + [体重] -> [{weight} kg],能猜出原意是"拖滑块改体重"。

不要用神秘缩写 / 自定义 syntax 让 plain-text 读者迷糊。

3. 关键内容不要藏 modal

\```modal trigger="详情"
(核心解释)
\```

→ Plain-text reader 看到 \```modal trigger="详情" + 内容全部展开——没有"折叠"效果。所以核心内容藏 modal = 在 plain-text 里也藏不住 = 你以为藏了其实没藏 + Rho reader 里反而藏了让用户看不到。

结论:modal 只藏"可选深度"。

4. annotate 的字符位置 / 颜色含义在标注表里写清楚

\```annotate
The quick brown fox
---
4-9|red|形容词
10-15|blue|名词
\```

→ Plain-text reader 看到字符位置 + 颜色 + 描述,手动数字符位置都能对应,理解原意。不要用没有 label 的标注——失去 fallback 价值。

5. STL 节点用语义名

\```stl
[A] -> [B] ::mod(rule="causal")     ← ⚠️ "A" / "B" 太抽象
[Rate_Increase] -> [Inflation_Rise] ::mod(rule="causal")     ← ✅ 语义名
\```

→ Plain-text reader 看到语义名能立刻理解节点是什么;A/B/X/Y 在 plain text 里就是字母。


何时违反这条原则是可接受的

几乎从不。但有两个例外场景:

1. 内部工具 / 个人脚本生成的 .md

如果文件只在你的 Rho-only pipeline 内流转(永远不会被人用 GitHub 看),可以放宽——但写下来你为什么放弃 fallback,便于未来回溯。

2. 在 Rho 内嵌 raw HTML / Vue / React 组件

部分集成 reader 可能扩展支持。这样的扩展只在该 reader 里有效,离开就坏。Rho 官方 spec 不鼓励这种模式——如果你做了,把它隔离在专门的 plugin / 章节,不要混进通用 docs。


Common pitfalls

1. 用 HTML 标签替代 Rho DSL

<details>
<summary>点击展开</summary>
内容
</details>

→ HTML <details> 在 GitHub / Obsidian 渲染成可折叠(合法),但:

  • 无法跟 Rho interact / computed 等结合
  • 跟 Rho 整体 brand 不一致
  • 失去 "纯 markdown" 优势

改用 Rho 的 Modal 一致性更好。

2. 用 raw HTML 做 layout

<div class="grid">
  <div>左</div>
  <div>右</div>
</div>

→ 在 GitHub 上不渲染 <div> 的 CSS(GitHub 剥 style)。改用 Rho 的 Layout。

3. 用 emoji 当语义信号但 plain-text 看不到结构

🟢 推荐
🔴 禁用
🟡 谨慎

→ Plain-text reader 看到只是 emoji 字符,没有视觉权重。改用 Callout:

> [!INFO]
> 推荐

> [!CAUTION]
> 禁用

> [!WARN]
> 谨慎

callout 在 fallback 里是 blockquote + [!TYPE] 字面量——结构信息保留。

4. 觉得 plain-text fallback 是"安全机制"

❌ 误解:可以用 modal / interact 藏敏感数据,反正 Rho 不支持的人看不到。 ✅ 事实:所有 Rho DSL 在 fallback 里全部内容明文可见。Rho 是 UX 工具,不是安全工具——敏感数据应该完全不放进 markdown。

5. 用嵌套破坏 plain-text 可读性

\```layout grid cols=4
:::card
\```layout grid cols=3
...嵌套 5 层...

→ 即使语法合法,plain-text reader 看到 30 个 ::: 和 ``` 标记完全看不懂结构。保持 ≤ 3 层嵌套,且每层职责清晰。详见 Nested DSL.

6. interact template 用神秘表达式

template stl:
[结果] -> [{(p*pow(1+r,n)-p)/(p*r):.4f}]     ← ❌ plain-text 读者完全看不懂

→ 改用 computed 拆开 + 给中间值名字:

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

Plain-text reader 看到 computed gain = ... + computed roi = ... + template ... [{roi:.4f}],能跟着读懂逻辑。

7. 假设 reader "肯定支持" Rho

读者拖一下下面的滑块就能看到结果:

\```interact
slider x 0 10 5 1
template stl: [{x}]
\```

→ 如果读者在 GitHub 上看,"拖"什么也没有。改写成兼容 plain-text:

下面是一个交互式滑块(在 Rho MD 中可拖动;在其他地方显示为代码):

\```interact
slider x 0 10 5 1
template stl: [{x}]
\```

让 plain-text 读者也明白发生了什么。


哲学:为什么这条不能让

Markdown 之所以是 markdown,不是因为它有什么花哨的能力——其他格式能力都比它强。Markdown 的胜利全靠简单 + 普适 + 不可摧毁。

破坏 plain-text fallback = 破坏 markdown 之所以是 markdown 的根。

Rho 是 markdown 的 next evolution——演化不是革命。我们扩展能力但保留根。这条原则永远不让步。

"如果用户的文件因为换工具而坏掉,你就毁了 markdown 的全部价值。"


See also

  • What is Rho? — Rho 的三条核心原则之一就是这条
  • Nested DSL — 嵌套依然 fallback
  • Shared state — namespace 也 fallback(namespace 字面量可见)
  • 各 capability 文档的 "Plain-text fallback behavior" 段