From a8fc8f1ea8c45adf0a3df8c55d6d47f253f8f1bd Mon Sep 17 00:00:00 2001 From: rubinsh Date: Mon, 16 Mar 2026 12:27:02 +0200 Subject: [PATCH] fix(set_layout): default to HUG sizing when enabling auto-layout (#125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- CHANGELOG.md | 6 +++ packages/core/src/tools/modify.ts | 5 +++ tests/engine/tools.test.ts | 63 ++++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ca187d2e..35edd45c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/packages/core/src/tools/modify.ts b/packages/core/src/tools/modify.ts index cdea45102..b674dfc9c 100644 --- a/packages/core/src/tools/modify.ts +++ b/packages/core/src/tools/modify.ts @@ -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 diff --git a/tests/engine/tools.test.ts b/tests/engine/tools.test.ts index 8da7c2dc1..d3a42efac 100644 --- a/tests/engine/tools.test.ts +++ b/tests/engine/tools.test.ts @@ -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', () => {