{"record":{"id":"f5395a0dae6bcb4e","repo":"nexu-io/open-design","slug":"unsupported-template-binding-path-rawpath","errorCode":null,"errorMessage":"unsupported template binding path: ${rawPath}","messagePattern":"unsupported template binding path: (.+?)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/live-artifacts/render.ts","lineNumber":73,"sourceCode":"\nfunction walkPath(root: unknown, segments: string[], rawPath: string): unknown {\n  let current: unknown = root;\n  for (const segment of segments) {\n    if (current === null || current === undefined) return '';\n    if (Array.isArray(current)) {\n      if (!/^\\d+$/.test(segment)) throw new Error(`invalid array segment in template binding path: ${rawPath}`);\n      current = current[Number(segment)];\n      continue;\n    }\n    if (typeof current !== 'object') return '';\n    current = (current as Record<string, unknown>)[segment];\n  }\n  return current ?? '';\n}\n\nfunction readTemplatePath(dataJson: BoundedJsonObject, rawPath: string): unknown {\n  const segments = rawPath.split('.');\n  if (segments.shift() !== 'data') throw new Error(`unsupported template binding path: ${rawPath}`);\n  return walkPath(dataJson, segments, rawPath);\n}\n\nfunction scalarOrThrow(value: unknown, binding: string): string {\n  if (Array.isArray(value) || (value !== null && typeof value === 'object')) {\n    throw new Error(`template binding must resolve to a scalar: ${binding}`);\n  }\n  return escapeHtmlTemplateValue(value);\n}\n\nfunction rootResolver(dataJson: BoundedJsonObject): BindingResolver {\n  return (binding) => {\n    if (!TEMPLATE_PATH.test(binding) || !binding.startsWith('data')) {\n      throw new Error(`invalid template binding path: ${binding}`);\n    }\n    return scalarOrThrow(readTemplatePath(dataJson, binding), binding);\n  };\n}","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/live-artifacts/render.ts#L55-L91","documentation":"Thrown by readTemplatePath when the first segment of a binding path (before the first '.') is not literally 'data'. All global bindings must root at the data object: {{data.title}}, {{data.items.0.name}}, etc. A path that does not start with 'data' is treated as unsupported at the root scope; loop-variable-scoped bindings are handled by a different resolver (childResolver) and never reach this check.","triggerScenarios":"Binding {{title}} (missing data. prefix); {{config.title}}; {{metadata.tags}}; any root binding whose head segment isn't 'data'. Note: child loop variables like {{item.label}} are valid inside a repeat because childResolver handles them first.","commonSituations":"Model omits the data. prefix (common in Handlebars/Mustache where the root is implicit); template ported from a framework that allows bare names; misunderstanding that only loop variables (inside a repeat) and 'data.*' are valid heads.","solutions":["Prefix root bindings with 'data.': {{data.title}} instead of {{title}}.","For loop content use a repeat directive and bind via the loop variable name: {{item.field}}."],"exampleFix":"// before\n<div>{{title}}</div>\n// after\n<div>{{data.title}}</div>","handlingStrategy":"validation","validationCode":"function assertRootedAtData(binding: string): void {\n  if (binding.split('.')[0] !== 'data') throw new Error(`binding must start with data.: ${binding}`);\n}","typeGuard":"function isDataRootedBinding(b: string): boolean { return b.startsWith('data.') || b === 'data'; }","tryCatchPattern":null,"preventionTips":["Always prefix root bindings with 'data.'.","Reserve non-data heads for loop variables inside repeat blocks only.","Add an authoring-time linter that flags bare-name bindings at the root."],"tags":["template-binding","path-resolution","live-artifacts","validation"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}