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

Open in Rho MD →