fix(set_layout): default to HUG sizing when enabling auto-layout (#125)

* fix(set_layout): default to HUG sizing when enabling auto-layout

When `set_layout` transitions a frame from `layoutMode: 'NONE'` to
auto-layout, it now sets `primaryAxisSizingMode` and
`counterAxisSizingMode` to `'AUTO'` (which maps to HUG internally).

Previously these remained at `'FIXED'`, causing the frame to keep its
original dimensions instead of shrinking/growing to fit children. This
made `h="hug"`, `justify="end"`, and `grow={1}` appear broken when
containers were created via the `set_layout` MCP tool (as opposed to
the JSX `render` path, which already defaulted to HUG).

The fix only applies when `direction` is provided and the frame was
previously in `layoutMode: 'NONE'` — updating spacing or alignment on
an existing auto-layout frame does not reset sizing modes.

* docs: add changelog entry for set_layout HUG sizing fix
This commit is contained in:
rubinsh 2026-03-16 12:27:02 +02:00 committed by GitHub
parent 63e204fc71
commit a8fc8f1ea8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 73 additions and 1 deletions

View file

@ -1,5 +1,11 @@
# Changelog
## Unreleased
### Fixes
- Fix `set_layout` tool not defaulting to HUG sizing when enabling auto-layout — frames now shrink/grow to fit children instead of keeping fixed dimensions
## 0.10.0 — 2026-03-15
### Performance

View file

@ -229,7 +229,12 @@ export const setLayout = defineTool({
return { error: 'Frame has no auto-layout. Pass direction ("HORIZONTAL" or "VERTICAL") to enable it.' }
}
const wasNone = raw?.layoutMode === 'NONE'
if (args.direction) node.layoutMode = args.direction as 'HORIZONTAL' | 'VERTICAL'
if (args.direction && wasNone) {
node.primaryAxisSizingMode = 'AUTO'
node.counterAxisSizingMode = 'AUTO'
}
if (args.spacing !== undefined) node.itemSpacing = args.spacing
if (args.align !== undefined) node.primaryAxisAlignItems = args.align
if (args.counter_align !== undefined) node.counterAxisAlignItems = args.counter_align

View file

@ -1,6 +1,6 @@
import { describe, expect, test } from 'bun:test'
import { ALL_TOOLS, FigmaAPI, SceneGraph } from '@open-pencil/core'
import { ALL_TOOLS, FigmaAPI, SceneGraph, computeAllLayouts } from '@open-pencil/core'
function setup() {
const graph = new SceneGraph()
@ -216,6 +216,67 @@ describe('set_layout', () => {
expect(node.paddingLeft).toBe(20)
expect(node.paddingTop).toBe(20)
})
test('defaults to HUG sizing when enabling auto-layout', () => {
const { graph, figma } = setup()
const frame = figma.createFrame()
frame.resize(300, 200)
const rawBefore = graph.getNode(frame.id)!
expect(rawBefore.layoutMode).toBe('NONE')
expect(rawBefore.primaryAxisSizing).toBe('FIXED')
expect(rawBefore.counterAxisSizing).toBe('FIXED')
const tool = ALL_TOOLS.find((t) => t.name === 'set_layout')!
tool.execute(figma, { id: frame.id, direction: 'VERTICAL' })
const rawAfter = graph.getNode(frame.id)!
expect(rawAfter.layoutMode).toBe('VERTICAL')
expect(rawAfter.primaryAxisSizing).toBe('HUG')
expect(rawAfter.counterAxisSizing).toBe('HUG')
})
test('preserves sizing modes when updating existing auto-layout', () => {
const { graph, figma } = setup()
const frame = figma.createFrame()
frame.resize(300, 200)
const tool = ALL_TOOLS.find((t) => t.name === 'set_layout')!
tool.execute(figma, { id: frame.id, direction: 'HORIZONTAL' })
// Manually set primary axis back to FIXED (user wants fixed width)
graph.updateNode(frame.id, { primaryAxisSizing: 'FIXED' })
expect(graph.getNode(frame.id)!.primaryAxisSizing).toBe('FIXED')
// Updating spacing should NOT reset sizing modes
tool.execute(figma, { id: frame.id, spacing: 24 })
const raw = graph.getNode(frame.id)!
expect(raw.primaryAxisSizing).toBe('FIXED')
expect(raw.counterAxisSizing).toBe('HUG')
})
test('HUG default enables hug-to-fit with 5 children', () => {
const { graph, figma } = setup()
const frame = figma.createFrame()
frame.resize(320, 100)
const tool = ALL_TOOLS.find((t) => t.name === 'set_layout')!
tool.execute(figma, { id: frame.id, direction: 'VERTICAL', spacing: 8, padding: 16 })
for (let i = 0; i < 5; i++) {
const child = figma.createRectangle()
child.resize(280, 50)
frame.appendChild(child)
}
computeAllLayouts(graph)
const node = graph.getNode(frame.id)!
expect(node.primaryAxisSizing).toBe('HUG')
// 16 (top pad) + 5*50 (children) + 4*8 (gaps) + 16 (bottom pad) = 314
expect(node.height).toBe(314)
})
})
describe('delete_node', () => {