nexu-io/open-design · error · Error
nested data-od-repeat is not supported
Error message
nested data-od-repeat is not supported
What it means
Thrown by renderFragment() after it strips the repeat element's own directive and then re-scans the remaining body with findRepeatDirective(). If a real (structurally-positioned) directive still exists in the body, the template is rejected because html_template_v1 supports only one level of repetition. Literal `data-od-repeat` text inside comments, quoted attribute values, or prose stays inert and does NOT trigger this — only a genuine second directive element does.
Source
Thrown at apps/daemon/src/live-artifacts/render.ts:253
const { openTagStart, tagName } = directive;
const spec = REPEAT_DIRECTIVE_SPEC.exec(directive.spec);
if (!spec?.[1] || !spec[2]) throw new Error(`invalid data-od-repeat directive: "${directive.spec}" (expected "item in data.path")`);
const varName = spec[1];
const arrayPath = spec[2];
const openTagEnd = findTagEnd(html, openTagStart);
const selfClosed = html[openTagEnd - 1] === '/';
const elementEnd = selfClosed ? openTagEnd + 1 : findElementEnd(html, tagName, openTagEnd + 1);
const element = html.slice(openTagStart, elementEnd);
// Strip only this element's own directive from its opening tag; a REAL
// directive anywhere in what remains means a nested repeat, which the
// contract does not support. Literal mentions inside the repeated body
// stay inert — same rule as the top-level scan.
const itemTemplate = element.replace(REPEAT_DIRECTIVE, '');
if (findRepeatDirective(itemTemplate)) {
throw new Error('nested data-od-repeat is not supported');
}
out += interpolateScalars(html.slice(cursor, openTagStart), resolve);
for (const item of readArray(arrayPath)) {
out += renderFragment(itemTemplate, childResolver(resolve, varName, item), readArray);
}
cursor = elementEnd;
}
return out;
}
export function renderHtmlTemplateV1(input: LiveArtifactRenderInput): LiveArtifactRenderOutput {
validateHtmlTemplateV1Security(input.templateHtml);
if (RAW_TEMPLATE_INTERPOLATION.test(input.templateHtml)) {
throw new Error('raw template interpolation is not supported');
}
View on GitHub (pinned to 5be4028344)
Solutions
- Flatten the data in data.json so a single repeat suffices (e.g. emit a flat list of cells each carrying its row context), then loop once.
- Pre-render the nested structure server-side / in the agent before writing data.json, and emit the resulting HTML as a non-template artifact.
- Split into sibling repeats at the top level instead of nesting (one repeat per row, each row's cells rendered via scalar bindings if count is fixed).
- Do not attempt to work around it by encoding the inner directive as text — the scanner specifically distinguishes real directives from literal text, so a real nested directive will always be caught.
Example fix
// before (unsupported)
<ul data-od-repeat="row in data.rows">
<li data-od-repeat="cell in row.cells">{{cell.label}}</li>
</ul>
// after — flatten in data.json to data.cells, loop once
<ul data-od-repeat="cell in data.cells">
<li>{{cell.label}} ({{cell.rowTitle}})</li>
</ul> Defensive patterns
Strategy: validation
Validate before calling
// Reject templates whose body contains a REAL (structurally positioned) nested directive.
// Reuse the renderer's own scanner to avoid false positives from literal text.
import { renderFragment } from './render'; // findRepeatDirective is not exported; mirror its logic or unit-test through renderFragment
function assertNoNestedRepeat(templateHtml: string): void {
const re = /\s*\bdata-od-repeat\s*=\s*"([^"]*)"/gi;
let m: RegExpExecArray | null;
let count = 0;
while ((m = re.exec(templateHtml))) {
const openIdx = templateHtml.lastIndexOf('<', m.index);
const afterOpen = templateHtml.slice(openIdx).split('>', 1)[0];
if (!afterOpen.includes('>')) count++;
}
if (count > 1) throw new Error('template nests data-od-repeat — flatten the data instead');
} Prevention
- Design data.json as flat lists so a single repeat suffices.
- Pre-render nested structures into static HTML outside the template pipeline if needed.
- Document the single-level-repeat contract for anyone authoring templates.
When it happens
Trigger: A repeat element nested inside another repeat element: `<ul data-od-repeat="row in data.rows"><li data-od-repeat="cell in row.cells">...</li></ul>`. Note: the inner directive must be a real element open-tag; `data-od-repeat` text inside a comment or inside another attribute's quoted value is correctly ignored.
Common situations: Rendering tabular data (rows of cells), category→item hierarchies, or any tree where the natural template nests two loops. The html_template_v1 contract is single-pass and explicitly disallows this to keep data-supplied text inert.
Related errors
- invalid data-od-repeat directive: "${directive.spec}" (expec
- unterminated tag in live artifact template
- unbalanced data-od-repeat element <${tagName}>
- data-od-repeat source is not an array: ${arrayPath}
- script elements are not supported in live artifact previews
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/195e600ab20e1ba5.
Report an issue: GitHub.