# How HKUDS/CLI-Anything Processes Nested Groups with Layouts Recursively

> Learn how HKUDS/CLI-Anything recursively processes nested groups with layouts. Discover the caching mechanism used to build complex structures efficiently.

- Repository: [✨Data Intelligence Lab@HKU✨/CLI-Anything](https://github.com/HKUDS/CLI-Anything)
- Tags: internals
- Published: 2026-08-16

---

**HKUDS/CLI-Anything processes nested groups with their own layouts recursively by calling `computeLayout` on each group's children whenever a layer of type `group` contains both `children` and a `layout` object, caching the results in `_childLayout` for the builder to materialize.**

HKUDS/CLI-Anything is a command-line tool that generates Sketch documents from JSON specifications. When working with complex UI hierarchies, the tool must handle nested groups with their own layouts processed recursively to calculate accurate positioning and dimensions at any depth. The layout engine achieves this through a depth-first traversal that resolves parent containers before their children using the core utilities in [`sketch/agent-harness/src/layout.js`](https://github.com/HKUDS/CLI-Anything/blob/main/sketch/agent-harness/src/layout.js).

## The Core Layout Engine

The layout logic lives in [`sketch/agent-harness/src/layout.js`](https://github.com/HKUDS/CLI-Anything/blob/main/sketch/agent-harness/src/layout.js), which exports the `computeLayout` function. This engine accepts an array of layer specifications, a layout description (**`vertical-stack`**, **`horizontal-stack`**, or **`absolute`**), and container dimensions. It returns an array of frame objects containing `x`, `y`, `width`, and `height` for each layer.

When the engine encounters a layer where `layer.type === 'group'`, it treats that layer as a miniature container. If the group contains `children`, the engine initiates recursive processing to resolve the nested coordinate system before finalizing the parent's geometry.

## Recursive Processing Logic

The recursion occurs in three specific layout contexts within [`layout.js`](https://github.com/HKUDS/CLI-Anything/blob/main/layout.js). In each case, the engine checks for the group type and children property, then invokes `computeLayout` with the group's local layout configuration.

### Vertical-Stack Layout Handling

In `layoutVerticalStack` (lines 96-108), the engine detects groups with their own layout definitions:

```javascript
if (layer.type === 'group' && layer.children && layer.layout) {
  const childFrames = computeLayout(
    layer.children,
    layer.layout,
    availableWidth,
    availableHeight
  );
  // Calculate group height from deepest child + padding
}

```

The function calls `computeLayout` using the group's specific layout configuration, obtains the child frames, and updates the parent group's height based on the deepest child's bottom edge plus any bottom padding.

### Horizontal-Stack Layout Handling

The `layoutHorizontalStack` function (lines 48-54) processes groups similarly:

```javascript
if (layer.type === 'group' && layer.children) {
  const childFrames = computeLayout(
    layer.children,
    layer.layout || { type: 'absolute' },
    availableWidth,
    availableHeight
  );
  // Recompute height if originally unspecified (0)
}

```

Here, `computeLayout` processes the children using either the group's supplied layout or falling back to absolute positioning. If the group's height is unspecified (`0`), the engine recomputes it from the maximum bottom edge of the resolved children.

### Absolute Layout Handling

Even in free-form positioning, the `layoutAbsolute` function (lines 7-12) maintains recursion:

```javascript
if (layer.type === 'group' && layer.children) {
  const childFrames = computeLayout(
    layer.children,
    layer.layout || { type: 'absolute' },
    layer.width || parentWidth,
    layer.height || parentHeight
  );
}

```

This ensures that nested groups receive proper frame calculations even when positioned arbitrarily within their parent, allowing completely free-form positioning inside the group while still resolving deeper hierarchies.

## Building the Visual Hierarchy

Each recursive call returns frame objects that the engine attaches to the parent specification under the private property **`_childLayout`**. The builder ([`sketch/agent-harness/src/builder.js`](https://github.com/HKUDS/CLI-Anything/blob/main/sketch/agent-harness/src/builder.js), lines 49-71) consumes this cached data to construct the actual Sketch object tree:

```javascript
// builder.js - handling a group
const children = buildLayerTree(
  spec.children || [], 
  spec._childLayout || [], 
  tokens
);
// ...
return primitives.createGroup(props, children);

```

This separation of concerns allows the layout engine to handle the complex mathematics of nested coordinate systems while the builder focuses on materializing Sketch primitives using `primitives.createGroup`.

## Working with Nested Layouts

### JSON Specification Example

Consider a document with an outer vertical stack containing an inner horizontal group:

```json
{
  "pages": [
    {
      "name": "Demo",
      "layers": [
        {
          "type": "group",
          "layout": { "type": "vertical-stack", "gap": 10, "paddingTop": 5 },
          "children": [
            { "type": "text", "value": "Header" },
            {
              "type": "group",
              "layout": { "type": "horizontal-stack", "gap": 5 },
              "children": [
                { "type": "text", "value": "Left" },
                { "type": "text", "value": "Right" }
              ]
            },
            { "type": "text", "value": "Footer" }
          ]
        }
      ]
    }
  ]
}

```

When executed via `npm run build -- spec.json out.sketch`, the engine first lays out the outer vertical-stack, then recursively processes the inner horizontal-stack to calculate final coordinates for every text layer before building the corresponding Sketch group hierarchy.

### Programmatic Layout Calculation

You can invoke the layout engine directly in Node.js to verify the recursive resolution:

```javascript
const { computeLayout } = require('./sketch/agent-harness/src/layout');

const spec = [
  {
    type: 'group',
    layout: { type: 'vertical-stack', gap: 8 },
    children: [
      { type: 'text', value: 'A' },
      {
        type: 'group',
        layout: { type: 'horizontal-stack', gap: 4 },
        children: [
          { type: 'text', value: 'B1' },
          { type: 'text', value: 'B2' }
        ]
      },
      { type: 'text', value: 'C' }
    ]
  }
];

const frames = computeLayout(spec, { type: 'vertical-stack' }, 300, 400);
console.log(frames);

```

The output demonstrates recursive resolution: the middle frame's dimensions reflect the combined width of its horizontal children while maintaining the vertical flow of the parent container, proving that the engine correctly processed the nested group with its own layout.

## Summary

- **Recursive descent**: The layout engine calls `computeLayout` whenever it encounters a group containing children, enabling unlimited nesting depth through repeated application of the same layout functions.
- **Layout inheritance**: Child groups can specify their own layout type (vertical-stack, horizontal-stack, or absolute) independent of their parents, allowing complex mixed-direction UIs.
- **Frame caching**: Calculated frames are stored in `_childLayout` properties, bridging the gap between the layout algorithm in [`layout.js`](https://github.com/HKUDS/CLI-Anything/blob/main/layout.js) and the Sketch builder in [`builder.js`](https://github.com/HKUDS/CLI-Anything/blob/main/builder.js).
- **Dynamic sizing**: Parent groups automatically resize based on the aggregate dimensions of their resolved children, accounting for padding and gaps defined in the group's layout configuration.

## Frequently Asked Questions

### How does the layout engine handle mixed layout types in nested groups?

Each group processes its children using its own specified layout type, allowing an outer vertical-stack to contain an inner horizontal-stack. The `computeLayout` function treats each group as an isolated coordinate system, translating child frames into the parent's coordinate space only after the recursive call returns resolved dimensions.

### What happens if a nested group does not specify a layout property?

When a group lacks a layout configuration, the engine defaults to absolute positioning (as seen in lines 48-54 of [`layout.js`](https://github.com/HKUDS/CLI-Anything/blob/main/layout.js)). Children retain their explicit x/y coordinates or default to (0,0), but the recursive processing still occurs to handle any deeper nested groups that might have their own layouts.

### Can arbitrarily deep nesting affect performance?

The algorithm uses depth-first recursion with O(n) complexity relative to the total layer count. Since each layer is processed exactly once and frames are cached in `_childLayout`, the engine efficiently handles deeply nested structures without redundant calculations or exponential overhead.

### Where does the final Sketch object construction occur?

After `computeLayout` completes, [`sketch/agent-harness/src/builder.js`](https://github.com/HKUDS/CLI-Anything/blob/main/sketch/agent-harness/src/builder.js) consumes the `_childLayout` arrays to construct the actual Sketch group hierarchy using `primitives.createGroup`, materializing the calculated geometry into Sketch document objects ready for export.