# How DESIGN.md Token Reference Resolution Works: A Deep Dive into the google-labs-code/design.md CLI

> Discover how DESIGN.md token reference resolution works. Learn about primitive parsing, chained resolution, and component property binding facilitated by the ModelHandler class.

- Repository: [Google Labs Code/design.md](https://github.com/google-labs-code/design.md)
- Tags: deep-dive
- Published: 2026-06-26

---

**DESIGN.md token references use the syntax `{path.to.token}` and are resolved through a three-phase process involving primitive parsing, chained reference resolution with cycle detection, and component property binding, all orchestrated by the `ModelHandler` class.**

The [`google-labs-code/design.md`](https://github.com/google-labs-code/design.md/blob/main/google-labs-code/design.md) repository implements a declarative design token system where YAML front-matter can reference previously defined values using curly-brace notation. Understanding this resolution mechanism is essential for debugging circular dependencies and extending the CLI linter. The entire pipeline lives in the TypeScript source code and enforces strict validation rules to prevent infinite recursion.

## The Three-Phase Resolution Pipeline

The `ModelHandler` class in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts) processes token references through a sequential pipeline that transforms raw strings into resolved objects.

### Phase 1: Primitive Token Parsing

The handler first iterates over top-level token groups—`colors`, `typography`, `rounded`, and `spacing`—to build an initial **symbol table**. Primitive values such as hex colors or dimensions are parsed using dedicated functions like `parseColor` and `parseDimension`, then inserted as resolved objects. Reference strings wrapped in curly braces are inserted as-is for subsequent processing.

This initial build occurs in lines 54-124 of [`handler.ts`](https://github.com/google-labs-code/design.md/blob/main/handler.ts), where the system populates both the symbol table and type-specific maps (`colors`, `spacing`, etc.).

### Phase 2: Chained Reference Resolution

Once primitives are stored, the system invokes `resolveReference(symbolTable, path, visited, depth)` to dereference any remaining token strings. This recursive function:

1. Validates that `depth` does not exceed `MAX_REFERENCE_DEPTH` (default 10)
2. Detects circular references using a `visited` Set
3. Looks up the current path in the symbol table
4. Recurses if the looked-up value is another reference
5. Returns the final resolved object or `null` on failure

The resolution logic spans lines 73-96 in [`handler.ts`](https://github.com/google-labs-code/design.md/blob/main/handler.ts). When a chain resolves successfully, lines 128-176 update the symbol table and token maps with the concrete values (e.g., `ResolvedColor` or `ResolvedDimension`).

### Phase 3: Component Property Resolution

Component definitions reference tokens in their property values. The handler walks each component and checks if property values match the reference pattern using `isTokenReference()`. Numbers and booleans pass through unchanged, while valid references trigger `resolveReference()` against the fully populated symbol table.

Failures during this phase are recorded in the `unresolvedRefs` array for reporting, while successful resolutions produce `ComponentDef` objects containing ready-to-use token instances.

## Configuration Limits and Safety Guards

The system prevents runaway recursion through configurable constants defined in [`packages/cli/src/linter/spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/spec-config.ts) (lines 24-27) and loaded via `getSpecConfig()`.

### Reference Depth Limits

**`MAX_REFERENCE_DEPTH`** caps reference chains at 10 hops by default. If `resolveReference()` exceeds this depth, it returns `null` and the model records a finding with severity `'error'`.

### Token Nesting Constraints

**`MAX_TOKEN_NESTING_DEPTH`** (default 20) limits how deeply objects can nest in the YAML tree. The `forEachLeaf` utility enforces this constraint during the initial parsing phase to maintain predictable traversal performance.

## Token Reference Syntax and Constraints

A valid reference must follow the exact pattern `{path.to.token}` where the path uses dot notation to traverse the token hierarchy. For example:

```yaml
colors:
  primary: "#1A1C1E"
  accent: "{colors.primary}"

```

The resolver expects terminal paths to point at primitive tokens. Composite values may only be referenced within the `components` section according to the specification documented in [`docs/spec.md`](https://github.com/google-labs-code/design.md/blob/main/docs/spec.md) (lines 86-88).

## Practical Resolution Examples

### Resolving Simple Color References

Given a DESIGN.md file with mutual references:

```yaml
colors:
  primary: "#1A1C1E"
  accent: "{colors.primary}"

```

The CLI executes the following resolution:

```typescript
// Phase 1: Primitive parsing
symbolTable.set('colors.primary', resolvedPrimary);      // ResolvedColor object
symbolTable.set('colors.accent', '{colors.primary}');   // Raw reference string

// Phase 2: Chained resolution
const resolved = resolveReference(symbolTable, 'colors.primary', new Set());
// Returns the ResolvedColor object for "#1A1C1E"
symbolTable.set('colors.accent', resolved);

```

The `accent` key now points to the same `ResolvedColor` instance as `primary`, enabling memory-efficient token reuse.

### Detecting Circular Reference Chains

Circular dependencies cause immediate resolution failures:

```yaml
colors:
  a: "{colors.b}"
  b: "{colors.c}"
  c: "{colors.a}"   # Creates a cycle

```

The resolver detects the cycle when `resolveReference()` encounters `'colors.a'` in the `visited` Set during recursion. It returns `null` and generates a finding:

```json
{
  "severity": "error",
  "path": "colors.a",
  "message": "Reference could not be resolved (missing or circular)."
}

```

### Binding Tokens to Component Properties

Components resolve references during the final phase:

```yaml
components:
  card:
    backgroundColor: "{colors.primary}"
    padding: "{spacing.md}"

```

The handler processes these properties:

```typescript
if (isTokenReference(rawValue)) {
  const refPath = rawValue.slice(1, -1); // Extract "colors.primary"
  const resolved = resolveReference(symbolTable, refPath, new Set());
  if (resolved !== null) {
    properties.set(propName, resolved);
  } else {
    unresolvedRefs.push(rawValue);
  }
}

```

The resulting `ComponentDef` contains fully resolved objects rather than raw strings, ready for downstream generators like Tailwind config exporters.

## Summary

- **DESIGN.md** uses `{path.to.token}` syntax for references, resolved by the `ModelHandler` class in [`packages/cli/src/linter/model/handler.ts`](https://github.com/google-labs-code/design.md/blob/main/packages/cli/src/linter/model/handler.ts).
- Resolution follows three phases: primitive parsing, chained reference resolution with cycle detection, and component property binding.
- **Safety limits**: `MAX_REFERENCE_DEPTH` (default 10) prevents infinite recursion, while `MAX_TOKEN_NESTING_DEPTH` (default 20) restricts YAML complexity.
- The `resolveReference()` function uses a `visited` Set to detect cycles and returns `null` for broken chains, which are recorded in the `findings` array.
- Component properties undergo the same resolution logic against the fully populated symbol table, producing `ComponentDef` objects with concrete token values.

## Frequently Asked Questions

### What is the maximum depth for token references in DESIGN.md?

The default maximum reference depth is **10 hops**, controlled by the `MAX_REFERENCE_DEPTH` constant in [`spec-config.ts`](https://github.com/google-labs-code/design.md/blob/main/spec-config.ts). Exceeding this limit causes the resolver to return `null` and emit an error finding, preventing stack overflow errors from deeply chained references.

### How does the resolver detect circular references?

The `resolveReference()` function accepts a `visited` Set that tracks all paths encountered during the current resolution chain. Before recursing into a new reference, it checks if the target path already exists in the Set. If found, the function returns `null` immediately and reports a circular dependency error.

### Can component properties reference tokens from any group?

Yes, component properties can reference any token previously loaded into the symbol table, including `colors`, `typography`, `spacing`, and `rounded` tokens. The resolution occurs during Phase 3 after all token groups have been processed, ensuring the full symbol table is available for lookups.

### What happens when a token reference cannot be resolved?

Unresolved references—whether from missing definitions, circular dependencies, or depth limit violations—cause `resolveReference()` to return `null`. During component processing, these failures are added to the `unresolvedRefs` array. The `ModelHandler` aggregates all such errors into the `findings` result, which the CLI linter surfaces as validation errors with specific path information.