Improve AI design quality: auto-infer flex, reduce false warnings, prompt patterns

Renderer: Frames with 2+ children auto-infer flex="col" when no explicit
flex/grid prop is set. Children with x/y keep ABSOLUTE positioning.
Extract shouldEnableAutoLayout() to stay under complexity limit.

Describe issues: suppress false positives that waste AI steps—
- checkNestedFlexWithoutFill: skip when parent uses justify=between/center
- checkGrowInHug/checkHugCollapse: skip when node will get concrete size
  from its own parent (grow>0, STRETCH, or effectively filling)
- checkSiblingHeightConsistency: skip when parent uses items=center

System prompt: add mandatory flex rule for multi-child frames,
tab-bar pattern, remove 'nested flex may collapse' from fix list.
This commit is contained in:
Anton A S 2026-03-13 12:21:10 +03:00
parent b5fd31b557
commit 933511f43e
3 changed files with 37 additions and 13 deletions

View file

@ -201,7 +201,8 @@ async function renderNode(graph: SceneGraph, tree: TreeNode, parentId: string):
const parentLayout = parent?.layoutMode ?? 'NONE'
const isText = nodeType === 'TEXT'
const overrides = propsToOverrides(tree.props, isText, parentLayout)
const childCount = tree.children.filter((c) => typeof c !== 'string' && isTreeNode(c)).length
const overrides = propsToOverrides(tree.props, isText, parentLayout, childCount)
if (isText) {
const textContent = tree.children.filter((c): c is string => typeof c === 'string').join('')
@ -414,13 +415,25 @@ function applyAutoLayoutSizing(
if (counterDim === 'hug') o.counterAxisSizing = 'HUG'
}
function shouldEnableAutoLayout(
props: Record<string, unknown>,
isText: boolean,
childCount: number
): boolean {
if (props.flex !== undefined) return true
if (!isText && hasAutoLayoutTriggerProps(props)) return true
if (!isText && !props.grid && childCount >= 2) return true
return false
}
function applyLayoutOverrides(
props: Record<string, unknown>,
o: Partial<SceneNode>,
w: unknown,
h: unknown,
isText: boolean,
parentLayout: SceneNode['layoutMode']
parentLayout: SceneNode['layoutMode'],
childCount: number
): void {
if (props.grid) {
applyGridOverrides(props, o, w, h)
@ -433,9 +446,7 @@ function applyLayoutOverrides(
applyGridChildOverrides(props, o)
}
const needsAutoLayout = props.flex !== undefined || (!isText && hasAutoLayoutTriggerProps(props))
if (needsAutoLayout) {
if (shouldEnableAutoLayout(props, isText, childCount)) {
applyAutoLayoutSizing(o, props, w, h)
}
@ -569,7 +580,8 @@ function applyShapeAndEffectOverrides(props: Record<string, unknown>, o: Partial
function propsToOverrides(
props: Record<string, unknown>,
isText: boolean,
parentLayout: SceneNode['layoutMode']
parentLayout: SceneNode['layoutMode'],
childCount = 0
): Partial<SceneNode> {
const o: Partial<SceneNode> = {}
@ -577,7 +589,7 @@ function propsToOverrides(
const { w, h } = applySizeOverrides(props, o, parentLayout)
applyVisualOverrides(props, o)
applyLayoutOverrides(props, o, w, h, isText, parentLayout)
applyLayoutOverrides(props, o, w, h, isText, parentLayout, childCount)
if (isText) applyTextOverrides(props, o, parentLayout)
applyShapeAndEffectOverrides(props, o)

View file

@ -101,9 +101,16 @@ function checkDividerOrientation(ctx: LayoutContext): void {
}
}
function willGetConcreteSize(node: SceneNode, isRow: boolean, graph: SceneGraph): boolean {
return node.layoutGrow > 0
|| node.layoutAlignSelf === 'STRETCH'
|| isEffectivelyFilling(node, isRow, graph)
}
function checkGrowInHug(ctx: LayoutContext): void {
const { node, children, issues } = ctx
const { node, isRow, graph, children, issues } = ctx
if (node.primaryAxisSizing !== 'HUG') return
if (willGetConcreteSize(node, isRow, graph)) return
for (const child of children) {
if (child.layoutGrow > 0) {
issues.push({
@ -164,9 +171,9 @@ function checkChildOverflow(ctx: LayoutContext): void {
}
function checkHugCollapse(ctx: LayoutContext): void {
const { node, isRow, children, issues } = ctx
const { node, isRow, graph, children, issues } = ctx
if (children.length === 0) return
if (node.primaryAxisSizing === 'HUG' && children.every((c) => c.layoutGrow > 0)) {
if (node.primaryAxisSizing === 'HUG' && !willGetConcreteSize(node, isRow, graph) && children.every((c) => c.layoutGrow > 0)) {
issues.push({
message: `"${node.name}" is HUG but all children use grow — collapses to zero`,
suggestion: 'Give at least one child a fixed size, or set parent to fixed'
@ -234,7 +241,8 @@ function checkTextOverflow(ctx: LayoutContext): void {
}
function checkSiblingHeightConsistency(ctx: LayoutContext): void {
const { isRow, children, issues } = ctx
const { node, isRow, children, issues } = ctx
if (node.counterAxisAlign === 'CENTER') return
const containers = children.filter((c) => CONTAINER_TYPES.has(c.type))
if (containers.length < 2) return
const dim = isRow ? 'height' : 'width'
@ -290,6 +298,7 @@ function checkAbsoluteInFlex(_ctx: LayoutContext): void {
function checkNestedFlexWithoutFill(ctx: LayoutContext): void {
const { node, isRow, children, issues } = ctx
if (node.layoutMode === 'NONE') return
if (node.primaryAxisAlign === 'SPACE_BETWEEN' || node.primaryAxisAlign === 'CENTER') return
for (const child of children) {
if (child.layoutMode === 'NONE') continue
const crossDim = isRow ? child.width : child.height

View file

@ -32,7 +32,9 @@ These are ALL available props. Nothing else exists.
## Layout rules
**Every parent with children using `w="fill"` or `h="fill"` MUST have `flex="col"` or `flex="row"`.** Without flex, fill is ignored. This is the #1 layout bug.
**Every Frame with 2+ children needs `flex="col"` or `flex="row"`.** Without it, children stack at (0,0). Card with photo + info → `flex="col"`. Row of buttons → `flex="row"`. Only omit for decorative layers with explicit x/y positioning.
**Every parent with children using `w="fill"` or `h="fill"` MUST have `flex="col"` or `flex="row"`.** Without flex, fill is ignored.
justify/items require flex. The value is "between", not "space-between".
@ -82,6 +84,8 @@ No style={{}}, className, CSS. No named colors or rgb(). No percentage values. N
**Don't mix `w={N}` and `grow={N}`** — grow overrides width.
**Tab bar / Bottom nav:** Outer frame `flex="row" w="fill" justify="between" px={32}`. Each tab `flex="col" items="center" gap={4}`. Tab items are HUG-width — `justify="between"` distributes them. Don't use `grow` on individual tabs.
**Dividers:** Vertical `w={1} h="fill"` inside flex="row". Horizontal `h={1} w="fill"` inside flex="col".
# Workflow (MANDATORY)
@ -102,7 +106,6 @@ Typically **3 renders + 34 describes**. `describe` the root with `depth=2`
- "gap N not on 8px grid" → fix the gap
- "grow inside HUG parent" → set parent to fixed size or use h="fill"
- "nested flex may collapse" → add w="fill" or grow
- "duplicate sibling names" → rename
- "near-invisible fill" → increase alpha