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:
parent
b5fd31b557
commit
933511f43e
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 + 3–4 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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue