{"record":{"id":"e278044b38c245f0","repo":"nexu-io/open-design","slug":"invalid-data-od-repeat-directive-directive-spe","errorCode":null,"errorMessage":"invalid data-od-repeat directive: \"${directive.spec}\" (expected \"item in data.path\")","messagePattern":"invalid data-od-repeat directive: \"(.+?)\" \\(expected \"item in data\\.path\"\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/live-artifacts/render.ts","lineNumber":238,"sourceCode":"    }\n    cursor = matchEnd;\n  }\n  return null;\n}\n\nfunction renderFragment(html: string, resolve: BindingResolver, readArray: ArrayReader): string {\n  let out = '';\n  let cursor = 0;\n  while (cursor < html.length) {\n    const directive = findRepeatDirective(html, cursor);\n    if (!directive) {\n      out += interpolateScalars(html.slice(cursor), resolve);\n      break;\n    }\n    const { openTagStart, tagName } = directive;\n\n    const spec = REPEAT_DIRECTIVE_SPEC.exec(directive.spec);\n    if (!spec?.[1] || !spec[2]) throw new Error(`invalid data-od-repeat directive: \"${directive.spec}\" (expected \"item in data.path\")`);\n    const varName = spec[1];\n    const arrayPath = spec[2];\n\n    const openTagEnd = findTagEnd(html, openTagStart);\n    const selfClosed = html[openTagEnd - 1] === '/';\n    const elementEnd = selfClosed ? openTagEnd + 1 : findElementEnd(html, tagName, openTagEnd + 1);\n    const element = html.slice(openTagStart, elementEnd);\n\n    // Strip only this element's own directive from its opening tag; a REAL\n    // directive anywhere in what remains means a nested repeat, which the\n    // contract does not support. Literal mentions inside the repeated body\n    // stay inert — same rule as the top-level scan.\n    const itemTemplate = element.replace(REPEAT_DIRECTIVE, '');\n    if (findRepeatDirective(itemTemplate)) {\n      throw new Error('nested data-od-repeat is not supported');\n    }\n\n    out += interpolateScalars(html.slice(cursor, openTagStart), resolve);","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/live-artifacts/render.ts#L220-L256","documentation":"Thrown by renderFragment() when the contents of a data-od-repeat attribute do not match the required directive grammar: `varName in data.path`. The REPEAT_DIRECTIVE_SPEC regex demands exactly one loop variable (identifier), the literal keyword `in`, and a data-rooted dotted path (must start with `data`). Any deviation — missing `in`, missing `data.` prefix, disallowed characters, multiple variables — is rejected.","triggerScenarios":"Writing `data-od-repeat=\"item in items\"` (forgot the `data.` prefix on the source); `data-od-repeat=\"items\"` (missing the `in` keyword); `data-od-repeat=\"item, idx in data.items\"` (multiple loop vars not supported); `data-od-repeat=\"item in data.items.list\"` is fine but `data-od-repeat=\"item-in data.items\"` (dash in var name) is not.","commonSituations":"Developer copies a Vue (`v-for=\"item in items\"`) or Angular (`*ngFor=\"let item of items\"`) snippet without translating to the html_template_v1 `data.*` convention; LLM authors a directive from memory and omits the `data.` prefix; using `of` instead of `in`.","solutions":["Rewrite the directive as `data-od-repeat=\"<varName> in data.<path>\"`, e.g. `data-od-repeat=\"item in data.items\"`.","Make sure the source path begins with `data.` and uses only allowed segment characters (letters, digits, underscore, dash; numeric indices allowed).","Use a single loop variable name made of `[A-Za-z_][A-Za-z0-9_]*` (no dashes).","If you need the index, expose it inside data.json (e.g. precompute `data.indexedItems` with explicit index fields) since the directive grammar does not support `(item, i) in ...`."],"exampleFix":"// before\n<li data-od-repeat=\"item in items\">{{item.label}}</li>\n// after\n<li data-od-repeat=\"item in data.items\">{{item.label}}</li>","handlingStrategy":"validation","validationCode":"const REPEAT_DIRECTIVE_SPEC = /^\\s*([A-Za-z_][A-Za-z0-9_]*)\\s+in\\s+(data(?:\\.(?:[A-Za-z_][A-Za-z0-9_-]*|\\d+))*)\\s*$/;\n\nfunction isValidRepeatDirective(spec: string): boolean {\n  const m = REPEAT_DIRECTIVE_SPEC.exec(spec);\n  return !!m && !!m[1] && !!m[2];\n}\n\n// scan the template before rendering\nfor (const [, spec] of templateHtml.matchAll(/\\bdata-od-repeat\\s*=\\s*\"([^\"]*)\"/gi)) {\n  if (!isValidRepeatDirective(spec)) throw new Error(`bad directive: ${spec}`);\n}","typeGuard":"function isRepeatDirectiveSpec(spec: string): spec is `${string} in data.${string}` {\n  return /^\\s*[A-Za-z_][A-Za-z0-9_]*\\s+in\\s+data(?:\\.(?:[A-Za-z_][A-Za-z0-9_-]*|\\d+))+\\s*$/.test(spec);\n}","tryCatchPattern":null,"preventionTips":["Always write directives as `<var> in data.<path>` with the literal `data.` prefix.","Use a single loop variable; do not attempt `(item, idx) in ...`.","Pre-validate agent-authored directives against the spec regex before persisting."],"tags":["live-artifacts","html-template","template-rendering","validation","directive"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}