nexu-io/open-design · error · Error

invalid array segment in template binding path: ${rawPath}

Error message

invalid array segment in template binding path: ${rawPath}

What it means

Thrown by walkPath during template binding resolution when the current value is an Array but the next path segment is not a decimal integer (not /^\d+$/). Arrays can only be indexed numerically inside a binding path; any non-numeric segment (e.g. a property name) on an array is a path/type mismatch and is rejected rather than silently returning empty, so the author knows the path disagrees with the data shape.

Source

Thrown at apps/daemon/src/live-artifacts/render.ts:61

    .replaceAll('"', '"')
    .replaceAll("'", ''');
}

/**
 * A binding resolver for one scope. Given a trimmed binding path (e.g.
 * `data.title` or a loop variable path like `item.label`) it returns the
 * already-escaped scalar string to substitute, or throws for an unsupported
 * path. Loop scopes delegate non-matching heads (including `data.*`) to their
 * parent so global bindings keep working inside a repeat.
 */
type BindingResolver = (binding: string) => string;

function walkPath(root: unknown, segments: string[], rawPath: string): unknown {
  let current: unknown = root;
  for (const segment of segments) {
    if (current === null || current === undefined) return '';
    if (Array.isArray(current)) {
      if (!/^\d+$/.test(segment)) throw new Error(`invalid array segment in template binding path: ${rawPath}`);
      current = current[Number(segment)];
      continue;
    }
    if (typeof current !== 'object') return '';
    current = (current as Record<string, unknown>)[segment];
  }
  return current ?? '';
}

function readTemplatePath(dataJson: BoundedJsonObject, rawPath: string): unknown {
  const segments = rawPath.split('.');
  if (segments.shift() !== 'data') throw new Error(`unsupported template binding path: ${rawPath}`);
  return walkPath(dataJson, segments, rawPath);
}

function scalarOrThrow(value: unknown, binding: string): string {
  if (Array.isArray(value) || (value !== null && typeof value === 'object')) {
    throw new Error(`template binding must resolve to a scalar: ${binding}`);

View on GitHub (pinned to 5be4028344)

Solutions

  1. If the value is a list you want to render, use the repeat directive: data-od-repeat="item in data.items" then bind {{item.name}}.
  2. If you need a single element, index it numerically: {{data.items.0.name}}.
  3. Confirm the data shape in data.json matches the path; if the source switched from object to array, update the template or restructure the data.

Example fix

// before (data.items is an array)
<div>{{data.items.name}}</div>
// after
<div data-od-repeat="item in data.items">{{item.name}}</div>
Defensive patterns

Strategy: type-guard

Validate before calling

function isArrayIndex(seg: string): boolean { return /^\d+$/.test(seg); }
function resolveArray(root: unknown[], path: string[]): unknown {
  let cur: unknown = root;
  for (const seg of path) {
    if (!Array.isArray(cur) || !/^\d+$/.test(seg)) throw new Error('invalid array segment');
    cur = cur[Number(seg)];
  }
  return cur;
}

Type guard

function isNumericSegment(seg: string): boolean { return /^\d+$/.test(seg); }

Prevention

When it happens

Trigger: Binding {{data.items.name}} where data.items is an array (should be data.items.0.name or a repeat loop); {{data.list.length}} on an array; any time a path treats an array element as an object before indexing into it.

Common situations: Model writes a property-style path over a list returned by the refresh source (e.g. public_github_repository_metric or git.summary); data shape changed (object became array) and the template wasn't updated; misunderstanding that arrays require numeric indices in this minimal DSL.

Related errors


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/4a91358830b0f8ba. Report an issue: GitHub.