Common pitfalls
跨所有 Rho 能力的常见陷阱总览——按类型分组。每一条带"症状 / 原因 / 修复"。 写完文档渲染没出来 / 行为怪异,先来这里查。
单个能力的 pitfalls 在各自文档里(Callout / Layout / 等)。本页只列跨能力 / 高频 / 高影响 的。
A. Markdown 语法层
A1. fenced code 嵌套反引号冲突
症状:嵌套块解析失败,外层 ``` 提前 close。
原因:内层 fenced block 反引号数量跟外层一样,markdown 提前 close 外层。
修复:内外层反引号数不同:
\````layout ← 4 反引号外层
:::card
\```interact ← 3 反引号内层
\```
:::
\````
A2. directive ::: 没关闭
症状:整个 block 不渲染,错误连锁影响后面的内容。
原因::::card / :::tab / :::step / :::event 缺对应 ::: 关闭。
修复:每个 :::xxx 开必有 ::: 关,保持配对。
A3. callout 中段被空行打断
症状:callout 只渲染了第一段。
原因:blockquote 内空行(不带 >)会断开 callout。
修复:段落间用只含 > 的空引用行:
> [!INFO]
> 第一段。
>
> 第二段(仍在 callout 内)。
B. DSL 选错
B1. 用 callout 做 layout
症状:3-5 个 callout 并排显示,垂直堆叠占满页面。
原因:callout 是单段强调,不是布局工具。
修复:用 Layout grid + cards。
B2. 用 layout 做严格序列
症状:3 张卡片并排,但用户必须按 1→2→3 顺序看。
原因:layout 是平级展示,不暗示顺序。
修复:用 Stepper。
B3. 用 modal 藏核心内容
症状:80% 的读者看不到关键解释,因为不点 modal。
原因:modal 是"按需深度",藏核心 = 等于没写。
修复:核心内容直接写在正文;modal 只藏可选深度。
B4. 用 stepper 当 timeline
症状:每个"step"其实是历史事件,不是教程步骤。
原因:stepper 暗示"按顺序学",timeline 暗示"时间线展示"。
修复:历史 / 里程碑用 Timeline;教程 / 配置用 stepper。
B5. 用 tabs 让用户必看全部
症状:tabs 切换显示,但每个 tab 都是必读 → 用户错过 2-3 个。
原因:tabs 把内容藏起来——读者只看默认那个。
修复:必读全部用 Layout grid 同时显示;tabs 适合"等价多视图选一个"。
C. interact / 状态层
C1. 跨块期待状态共享但忘 namespace
症状:第 2 块 template 显示 {x} 字面量或抛 "undefined"。
原因:默认每个 interact 块独立——状态不共享。
修复:两个块都加同一 namespace:
\```interact namespace foo
slider x 0 10 5 1
\```
\```interact namespace foo
template stl: [{x}]
\```
详见 Shared state.
C2. 同 namespace 重复声明同名变量
症状:行为未定义(可能覆盖、可能错乱、可能保留第一次)。
原因:namespace 内每个变量只能声明一次。
修复:第一块声明所有 sliders / computed,后续块只引用,不重复声明。
C3. variable 名含特殊字符
症状:解析失败或行为意外。
原因:变量名只允许 [A-Za-z0-9_]。
修复:用驼峰 myVar 或下划线 my_var,不要空格 / 连字符 / 特殊字符。
C4. button value 含表达式但忘加引号
症状:解析失败或 button 行为不对。
原因:value 含 {expr} 时必须包在引号里。
修复:
button inc "+1" "{counter+1}" ← ✅
button inc "+1" {counter+1} ← ❌ 缺引号
C5. input 数值要做数学运算
症状:{age + 1} 输出 "301"(字符串拼接)而不是 31。
原因:input 永远返回字符串。
修复:数值输入用 slider;非要用 input 就 cast:{parseFloat(age) + 1}。
D. mini DSL 表达式
D1. 引用未声明变量
症状:template 显示 {y} 字面量或 "undefined" 错。
原因:所有 template 引用的变量必须先用 slider/input/select/toggle/computed 声明。
修复:检查变量是否在同 namespace(或同块)声明。
D2. 表达式优先级混淆
症状:BMI / 复利等公式算错。
原因:mini DSL 优先级跟数学 / JS 一致,但不显式括号容易踩坑。
修复:用括号显式表达:
computed bmi = weight / (height * height) ← ✅
computed bmi = weight / height ** 2 ← ⚠️ 优先级容易错
D3. 用了 mini DSL 不支持的函数
症状:表达式抛错或返回 undefined。
原因:mini DSL 不是 JS——Math.X / console.log / Date 等不存在。
修复:参考 Computed 内置函数列表。
D4. computed 循环依赖
症状:所有 computed 报错或显示 NaN。
原因:a = b + 1 + b = a + 1 形成环。
修复:computed 之间不能形成环。要"前次值"模式用 button self-reference。
D5. 三元嵌套太深
症状:表达式难读、易错、难维护。
原因:3 层以上三元嵌套人脑难解析。
修复:拆成多个 computed,或用多行格式:
computed grade = score >= 90 ? "A"
: score >= 80 ? "B"
: score >= 70 ? "C"
: "F"
E. Vega-Lite chart
E1. JSON 语法错
症状:chart 不渲染,控制台 JSON parse error。
原因:vega-lite spec 是严格 JSON——双引号 key、value 后逗号、严格格式。
修复:用 Vega-Lite Editor 验证 spec。
E2. {var} 占位符位置错
症状:chart 不渲染或行为意外。
原因:占位符替换发生在 JSON parse 之前——数字字段不带引号、字符串字段带引号。
修复:
- 数字:
"domain": [-{maxY}, {maxY}] - 字符串:
"mark": "{showArea ? "area" : "line"}"
E3. data.sequence 范围太大
症状:浏览器卡死。
原因:超过 5000 数据点 vega-lite 渲染压力极大。
修复:控制点数 < 5000;曲线 < 500 点已经看着平滑。
E4. transform.calculate 用 mini DSL syntax
症状:calculate 表达式不工作。
原因:vega-lite 用 vega expression,跟 mini DSL 不完全兼容。
修复:参考 vega expression 文档。
F. SVG scenes
F1. 缺 viewBox
症状:SVG 缩放行为不可预期。
原因:缺 viewBox 内部坐标系不定。
修复:总是设 viewBox:<svg viewBox="0 0 100 100">。
F2. 写了 <script> / <foreignObject> / on* 事件
症状:这些元素被剥离,对应功能不工作。
原因:Rho SVG sanitizer 故意阻止——安全设计。
修复:用纯声明式 SVG(标准形状 + animate 标签 + 占位符),不靠 JS。
F3. mini DSL 表达式用 Math.X
症状:Math.sin 等抛错。
原因:mini DSL 不是 JS,没 Math 全局。
修复:直接用 sin(x) / cos(x) / pow(a,b) / 等。
F4. SVG 元素过多
症状:动画卡顿。
原因:SVG 是 DOM——元素 > 200 后渲染压力大。
修复:减少元素 / 简化形状 / 改用 chart。
G. Timer / 动画
G1. step 太小
症状:浏览器卡。
原因:(max - min) / step > 数千帧。
修复:保持总帧数 100-1000 范围。
G2. timer 在 chart 但 calculate 没引用 timer 变量
症状:chart 静止,timer "白跑"。
原因:calculate 表达式不依赖 {t}。
修复:在 vega 表达式里显式引用 {t}:"sin(datum.x + {t})"。
G3. 多 timer 期待严格同步
症状:两个 timer 显示出 phase 不同步。
原因:每个 timer 独立调度——可能微小漂移。
修复:用 1 个 timer + 多个 computed 派生:
timer t 0 10 0.1 loop
computed t1 = t
computed t2 = (t + 5) % 10
H. 嵌套 / 复杂度
H1. 嵌套 4 层以上
症状:plain-text reader 看不懂结构;Rho 渲染性能差。
原因:每嵌套一层 inner processor 跑一次。
修复:保持 ≤ 3 层;重新设计页面结构。
H2. 同类型嵌套(tabs 套 tabs / stepper 套 stepper)
症状:UI 混乱,读者分不清外层内层。
原因:两层进度条 / 两组 tab bar 视觉冲突。
修复:把内层换成不同容器(modal / accordion / 列表)。
H3. interact 嵌入 modal
症状:modal 关再开 slider 状态丢失。
原因:modal hidden 时 hydration state 通常被清。
修复:核心 demo 别藏 modal——直接放正文。
I. Plain-text fallback
I1. 用 raw HTML 替代 Rho DSL
症状:在 Rho 里渲染 OK,但 GitHub / 其他 reader 不渲染 CSS。
原因:raw HTML 在多 reader 不一致;GitHub 剥 style。
修复:用 Rho DSL(Layout / Modal / 等)替代。
I2. 把 plain-text fallback 当安全机制
症状:试图用 modal 藏敏感信息——但 plain-text 全都看得到。
原因:fallback 是"读者依然能读",不是"对方看不到"。
修复:敏感数据不要进 markdown——根本不应该在文档里。
I3. 用没 label 的 emoji 当结构信号
症状:plain-text reader 看到一堆 emoji 没法理解结构。
原因:emoji 视觉上有意义,纯文本里只是字符。
修复:用 Callout 等结构化容器。
J. 性能
J1. 单页 interact 块过多
症状:page 加载慢、操作有延迟。
原因:每个 interact 块独立 hydrate,hydration 累加。
修复:合并相关 interact 用 namespace 共享;或拆到多页。
J2. chart 数据点过多 + timer 高频
症状:拖滑块 / timer 跳帧时浏览器卡。
原因:每帧重渲染 N 个数据点。
修复:减点数 + 增 step(降帧率)。
K. AI / LLM 生成
K1. LLM 把 mini DSL 当 JS
症状:生成的 computed 有 Math.sin / if/else / function。
原因:LLM 默认认 JS。
修复:在 prompt 里明确:"use Rho mini DSL — NOT JavaScript. Functions: pow, sqrt, sin, cos, abs, ... See [Rho LLM Protocol]."
K2. LLM 忘加 namespace
症状:多个 interact 块期待共享但实际隔离。
原因:LLM 可能不知 namespace 必填。
修复:在 prompt 里给 namespace 例子。详见 Rho AI / LLM Guide.
K3. LLM 写非法 directive 顺序
症状::::card 没在 layout 里 / :::step 没在 stepper 里。
原因:LLM 不知 directive 必须配父容器。
修复:prompt 强调 "directives must be inside their proper container"。
See also
- Plain-text fallback principle — fallback 哲学
- Nested DSL — 嵌套规则
- Shared state (namespace) — 状态共享
- 各 capability 文档的 "Common pitfalls" 段
- Examples gallery — 看正确用法