Common pitfalls
Cross-capability pitfall reference — grouped by category. Each entry: symptom / cause / fix. When your doc doesn't render right or behaves oddly, look here first.
Per-capability pitfalls live in their own pages (Callout / Layout / etc.). This page covers cross-capability / high-frequency / high-impact issues only.
A. Markdown syntax layer
A1. Fenced code nesting — backtick conflict
Symptom: nested block fails to parse; outer ``` closes early.
Cause: inner fenced block uses the same backtick count as the outer.
Fix: outer and inner backtick counts must differ:
````layout ← 4 backticks outer
:::card
```interact ← 3 backticks inner
```
:::
````
A2. Directive ::: not closed
Symptom: the whole block doesn't render; cascading errors affect content after it.
Cause: :::card / :::tab / :::step / :::event missing matching ::: close.
Fix: every :::xxx open needs a matching ::: close — always paired.
A3. Callout interrupted by mid-section blank line
Symptom: only the first paragraph renders inside the callout.
Cause: a blank line (without >) inside a blockquote breaks the callout.
Fix: use a blank quoted line (just >) between paragraphs:
> [!INFO]
> First paragraph.
>
> Second paragraph (still inside the callout).
B. Wrong DSL choice
B1. Using callout for layout
Symptom: 3-5 callouts stack vertically and dominate the page.
Cause: callout is single-passage emphasis, not a layout tool.
Fix: use Layout grid + cards.
B2. Using layout for strict sequence
Symptom: 3 cards side by side, but users must follow 1→2→3 order.
Cause: layout shows peers; doesn't imply sequence.
Fix: use Stepper.
B3. Hiding core content in modal
Symptom: 80% of readers miss the key explanation because they don't click.
Cause: modal is "depth on demand"; hiding core content = effectively unwritten.
Fix: put core content directly in main text; modal hides only optional depth.
B4. Stepper used as timeline
Symptom: each "step" is actually a historical event, not a tutorial step.
Cause: stepper implies "learn in order"; timeline implies "time-axis display."
Fix: history / milestones → Timeline; tutorials / setup → stepper.
B5. Tabs used when all content must be seen
Symptom: tabs hide content — readers see only the default and miss 2-3 others.
Cause: tabs hide non-active content.
Fix: must-see-all → Layout grid showing simultaneously; tabs is for "pick one of equivalent views."
C. interact / state layer
C1. Cross-block state sharing — forgot namespace
Symptom: block 2's template shows {x} literal or throws "undefined."
Cause: by default each interact block is isolated — state isn't shared.
Fix: both blocks declare the same namespace as their first body line:
```interact
namespace foo
slider x range=0..10 step=1 default=5
```
```interact
namespace foo
template stl: [{x}]
```
See Shared state.
C2. Same variable redeclared in same namespace
Symptom: undefined behavior (may overwrite, may error, may keep first).
Cause: each variable in a namespace has a single declaration.
Fix: declare all sliders / computeds in the first block; subsequent blocks reference only, don't redeclare.
C3. Variable name with special chars
Symptom: parse fails or unexpected behavior.
Cause: variable names allow only [A-Za-z0-9_].
Fix: use camelCase myVar or underscore my_var; no spaces / hyphens / specials.
C4. namespace written as a fence argument
Symptom: the namespace is silently ignored — blocks stay isolated.
Cause: namespace is a body line, not a fence-line argument.
Fix:
```interact
namespace foo ← ✅ own line, first in the body
slider x range=0..10 step=1 default=5
```
Never write the namespace on the fence line itself.
C5. Trying to do math on input value
Symptom: {age + 1} shows NaN or the literal text instead of 31.
Cause: input (like chip / select / toggle) always yields a string; strings don't do arithmetic.
Fix: numeric input → use slider (or timer). Reserve input for free text that is substituted verbatim into the template.
D. mini DSL expressions
D1. Reference to undeclared variable
Symptom: template shows {y} literal or throws "undefined."
Cause: every template variable must first be declared via slider/input/select/toggle/computed.
Fix: check variable is declared in the same namespace (or block).
D2. Operator precedence confusion
Symptom: BMI / compound interest etc. computes wrong.
Cause: mini DSL precedence matches math / JS, but implicit precedence is easy to miscount.
Fix: use explicit parentheses when mixing operators:
computed bmi = weight / (height * height) ← ✅ Clearest
computed bmi = weight / height ** 2 ← ✅ Also correct (** binds tighter than /)
computed x = a + b / 2 ← ⚠️ Is that (a+b)/2? Parenthesize your intent
D3. Used a function mini DSL doesn't support
Symptom: expression throws or returns undefined.
Cause: mini DSL isn't JS — Math.X / console.log / Date etc. don't exist.
Fix: see Computed function list.
D4. computed circular dependency
Symptom: all computeds error or show NaN.
Cause: a = b + 1 + b = a + 1 forms a cycle.
Fix: computeds can't form cycles — restructure so values flow one way, e.g. drive both from a shared slider or timer variable.
D5. Ternary nested too deep
Symptom: expression unreadable, error-prone, hard to maintain.
Cause: 3+ levels of nested ternary exceeds human readability.
Fix: split into multiple computeds, or use multi-line format:
computed grade = score >= 90 ? "A"
: score >= 80 ? "B"
: score >= 70 ? "C"
: "F"
E. Vega-Lite chart
E1. JSON syntax error
Symptom: chart doesn't render; console shows JSON parse error.
Cause: vega-lite spec is strict JSON — double-quote keys, comma between values, strict format.
Fix: use Vega-Lite Editor to validate.
E2. {var} placeholder in wrong position
Symptom: chart doesn't render or behaves unexpectedly.
Cause: replacement happens before JSON parse — numeric fields no quotes, string fields with quotes.
Fix:
- Numeric:
"domain": [-{maxY}, {maxY}] - String:
"mark": "{showArea ? "area" : "line"}"
E3. data.sequence range too large
Symptom: browser hangs.
Cause: > 5000 data points strains vega-lite rendering.
Fix: keep points < 5000; curves look smooth at < 500 points.
E4. transform.calculate using mini DSL syntax
Symptom: calculate expression doesn't work.
Cause: vega-lite uses vega expression, not fully compatible with mini DSL.
Fix: see vega expression docs.
F. SVG scenes
F1. Missing viewBox
Symptom: SVG scaling is unpredictable.
Cause: missing viewBox leaves internal coordinate system undefined.
Fix: always set viewBox: <svg viewBox="0 0 100 100">.
F2. Used <script> / <foreignObject> / on* events
Symptom: those elements get stripped; corresponding features don't work.
Cause: Rho's SVG sanitizer deliberately blocks them — safety design.
Fix: use pure declarative SVG (standard shapes + animate tags + placeholders); don't rely on JS.
F3. mini DSL expression uses Math.X
Symptom: Math.sin etc. throws.
Cause: mini DSL isn't JS; no Math global.
Fix: use sin(x) / cos(x) / pow(a,b) / etc. directly.
F4. Too many SVG elements
Symptom: animation stutters.
Cause: SVG is DOM — > 200 elements strains rendering.
Fix: reduce elements / simplify shapes / switch to a chart.
G. Timer / animation
G1. step too small
Symptom: browser hangs.
Cause: (max - min) / step > thousands of frames.
Fix: keep total frames in 100-1000 range.
G2. Timer paired with chart but calculate doesn't use timer var
Symptom: chart static, timer "runs for nothing."
Cause: calculate doesn't depend on {t}.
Fix: explicitly reference {t} in vega expression: "sin(datum.x + {t})".
G3. Multiple timers expected to be in strict sync
Symptom: two timers display out-of-sync phases.
Cause: each timer is independently scheduled — possible micro-drift.
Fix: use 1 timer + multiple computed derivations:
timer t range=0..10 step=0.1 loop=true
computed t1 = t
computed t2 = (t + 5) % 10
H. Nesting / complexity
H1. Nesting 4+ levels
Symptom: plain-text readers can't see structure; Rho rendering performance drops.
Cause: each nest level runs the inner processor again.
Fix: keep ≤ 3 levels; redesign page structure.
H2. Same-type nesting (tabs in tabs / stepper in stepper)
Symptom: UI confusion; readers can't tell outer from inner.
Cause: two progress bars / two tab bars compete visually.
Fix: switch the inner to a different container (modal / accordion / list).
H3. interact inside modal
Symptom: closing and reopening the modal loses slider state.
Cause: hydration state typically clears when modal hides.
Fix: don't hide core demos in modals — put them in main text.
I. Plain-text fallback
I1. Using raw HTML in place of AINP blocks
Symptom: renders OK in Rho, but on GitHub / other readers the CSS doesn't render.
Cause: raw HTML is inconsistent across readers; GitHub strips style.
Fix: use AINP blocks (Layout / Modal / etc.) instead.
I2. Treating plain-text fallback as a security mechanism
Symptom: trying to hide sensitive info in modals — but plain-text shows everything.
Cause: fallback means "readers can still read," not "others can't see."
Fix: sensitive data shouldn't enter markdown — it shouldn't be in the doc at all.
I3. Using unlabeled emoji as structural signals
Symptom: plain-text readers see emojis without context, can't understand structure.
Cause: emojis carry visual meaning; in plain text they're just characters.
Fix: use Callout etc. as structured containers.
J. Performance
J1. Too many interact blocks on one page
Symptom: slow page load; operations feel laggy.
Cause: each interact block hydrates independently; hydration cost adds up.
Fix: merge related interacts via namespace sharing; or split across multiple pages.
J2. Charts with many data points + high-frequency timers
Symptom: dragging sliders / timer updates cause browser lag.
Cause: each frame re-renders N data points.
Fix: reduce points + increase step (lower frame rate).
K. AI / LLM generation
K1. LLM treats mini DSL as JS
Symptom: generated computed has Math.sin / if/else / function.
Cause: LLM defaults to JS knowledge.
Fix: in prompt explicitly say: "use the AINP mini DSL — NOT JavaScript. Functions: pow, sqrt, sin, cos, abs, ..." (the full whitelist is in Computed Values).
K2. LLM forgets namespace
Symptom: multiple interact blocks meant to share state but actually isolated.
Cause: LLM may not know namespace is required.
Fix: give namespace examples in the prompt — see Shared State for correct ones.
K3. LLM writes invalid directive order
Symptom: :::card outside a layout / :::step outside a stepper.
Cause: LLM doesn't know directives need their parent containers.
Fix: prompt emphasizes "directives must be inside their proper container."
See also
- Plain-text fallback principle — Fallback philosophy
- Nested DSL — Nesting rules
- Shared state (namespace) — State sharing
- "Common pitfalls" sections in each capability doc
- Examples gallery — See correct usage patterns