feat(ai): refine prompts for text layout direction, badge limits, phone labels, and hero layout
- Differentiate text width strategy: vertical layouts use fill_container, horizontal rows use fit_content - Tighten badge/chip char limits (CJK <=8 / Latin <=16), reject longer text for badge style - Allow one centered placeholder label inside phone mockup frames instead of forcing empty - Add hero+phone two-column layout hint for desktop - Add prompt optimizer detectors for dense cards, table structure, and hero phone layout - Inject native dense-card, table, and hero instructions into sub-agent prompts
This commit is contained in:
parent
fa234e7233
commit
4367d2613a
|
|
@ -23,7 +23,7 @@ TEXT RESIZING (textGrowth):
|
|||
- "auto" = Auto Width: text expands horizontally, no word wrapping. Best for short labels, buttons, single-line text.
|
||||
- "fixed-width" = Auto Height: width is fixed (or "fill_container"), height auto-sizes to wrapped content. Best for paragraphs, descriptions, multi-line text.
|
||||
- "fixed-width-height" = Fixed Size: both width and height are fixed. Content clips if too long.
|
||||
- DEFAULT RULE: text inside layout frames should use textGrowth="fixed-width" + width="fill_container". This ensures text wraps within the parent and height auto-sizes.
|
||||
- DEFAULT RULE: in vertical layout frames, body/description text should use textGrowth="fixed-width" + width="fill_container". In horizontal rows, short labels should use width="fit_content" (or omit width) + textGrowth="auto" to avoid squeezing siblings.
|
||||
- Short labels/buttons can omit textGrowth (defaults to "auto").
|
||||
|
||||
TEXT TYPOGRAPHY:
|
||||
|
|
@ -41,12 +41,12 @@ RULES:
|
|||
- Use justifyContent="space_between" to spread items across full width (great for navbars, footers)
|
||||
|
||||
OVERFLOW PREVENTION (CRITICAL — violations cause visual glitches):
|
||||
- TEXT WIDTH: text nodes inside layout frames MUST use width="fill_container" + textGrowth="fixed-width". NEVER set a fixed pixel width on text inside a layout — it WILL overflow. The layout engine auto-constrains fill_container text to the parent's available content area.
|
||||
- TEXT WIDTH: for text inside vertical layout frames, use width="fill_container" + textGrowth="fixed-width". For text inside horizontal rows (nav/footer/button rows), default to width="fit_content" (or omit width) + textGrowth="auto". NEVER set fixed pixel width on text inside a layout.
|
||||
BAD: {"type":"text","width":378,"textGrowth":"fixed-width"} inside a 195px card with 80px padding → overflows!
|
||||
GOOD: {"type":"text","width":"fill_container","textGrowth":"fixed-width"} → auto-fits to 115px available space.
|
||||
- CHILD SIZE: any child with a fixed pixel width must be ≤ parent's content area (parent width − total horizontal padding). If unsure, use "fill_container".
|
||||
- CJK TEXT (Chinese/Japanese/Korean): each character renders at ~1.0× fontSize width. For buttons/badges containing CJK text, ensure: container width ≥ (charCount × fontSize) + total horizontal padding. Example: "免费下载" (4 chars) at fontSize 15 → needs ~60px content + padding → button width ≥ 104px with padding [8,22].
|
||||
- BADGES: when badge text is long or CJK, use width="fit_content" on the badge frame so it auto-sizes to its text content.
|
||||
- BADGES: use badge/chip style only for short labels (CJK <=8 chars / Latin <=16 chars). If text is longer, do NOT use badge style; use a normal text row or small card.
|
||||
`
|
||||
|
||||
const DESIGN_EXAMPLES = `
|
||||
|
|
@ -63,7 +63,7 @@ ICONS & IMAGES:
|
|||
IMPORTANT: Give icon nodes a descriptive name matching standard icon names (e.g. "SearchIcon", "MenuIcon", "ArrowRightIcon", "CheckIcon", "StarIcon", "DownloadIcon", "PlayIcon", "ShieldIcon", "ZapIcon", "HeartIcon", "UserIcon", "HomeIcon", "MailIcon", "BellIcon", "SettingsIcon", "PlusIcon", "EyeIcon", "LockIcon", "PhoneIcon", "ChevronRightIcon", "ChevronDownIcon", "XIcon").
|
||||
The system will auto-resolve icon names to verified SVG paths, so the name is more important than the d data.
|
||||
- Never use emoji characters as icons (e.g. 🧠✨📱✅). Always use path nodes for icons.
|
||||
- For app screenshot/mockup areas, use a phone placeholder frame with solid fill matching the page theme + 1px subtle stroke. cornerRadius ~32. No text inside — just a clean phone shape.
|
||||
- For app screenshot/mockup areas, use a phone placeholder frame with solid fill matching the page theme + 1px subtle stroke. cornerRadius ~32. Prefer no inner content; if a placeholder copy is needed (e.g. "APP截图占位"), keep exactly one centered text node INSIDE the phone frame (never as a sibling below it).
|
||||
- Do NOT use random real-world app screenshots or dense mini-app simulations for showcase sections.
|
||||
`
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ LAYOUT ENGINE (flexbox-based):
|
|||
- WIDTH CONSISTENCY: siblings in a vertical layout must use the SAME width strategy. If one input/button uses "fill_container", ALL sibling inputs/buttons must also use "fill_container". Mixing fixed-px and fill_container causes misalignment.
|
||||
- NEVER use "fill_container" on children of a "fit_content" parent — circular dependency breaks layout.
|
||||
- For two-column layouts: root (vertical) → content row (horizontal) → left column + right column. Each column uses "fill_container" width.
|
||||
- TEXT IN LAYOUTS (MOST COMMON BUG — read carefully): text inside layout frames MUST use textGrowth="fixed-width" + width="fill_container". NEVER use a fixed pixel width (e.g. width:378) on text inside a layout — the text WILL overflow its parent. "fill_container" auto-constrains to available space.
|
||||
- TEXT IN LAYOUTS (MOST COMMON BUG — read carefully): in vertical layouts, body text should use textGrowth="fixed-width" + width="fill_container". In horizontal rows, labels should use textGrowth="auto" + width="fit_content" (or omit width). NEVER use fixed pixel width (e.g. width:378) on text inside a layout.
|
||||
- SHORT TEXT: buttons, labels, single-line text can use textGrowth="auto" (or omit it) — text expands horizontally to fit content.
|
||||
- TEXT HEIGHT: NEVER set explicit pixel height on text nodes (e.g. height:22, height:44). OMIT the height property entirely — the layout engine auto-calculates height from textGrowth + content. Setting a small height causes text clipping and overlap with siblings below.
|
||||
- CJK BUTTONS/BADGES: Chinese/Japanese/Korean characters are wider. For a button with CJK text, ensure container width ≥ (charCount × fontSize) + horizontal padding. Example: "免费下载" (4 chars) at 15px → min content width ~60px → button width ≥ 60 + left padding + right padding.
|
||||
|
|
@ -185,7 +185,7 @@ DESIGN GUIDELINES:
|
|||
- Text: titles 22-28px bold, body 14-16px, captions 12px
|
||||
- Buttons: height 44-52px, cornerRadius 8-12, padding [12, 24] (vertical, horizontal). With icon+text: layout="horizontal", gap=8, alignItems="center". Width: "fill_container" (stretch), "fit_content" (hug), or fixed px — choose per context.
|
||||
- Icon-only buttons (heart, bookmark, share, etc.): square frame 44x44px, justifyContent="center", alignItems="center", path icon 20-24px inside.
|
||||
- Badges/tags ("NEW", "SALE", "PRO"): frame with padding [4, 12], cornerRadius 4-6, height="fit_content". Small text (11-13px), no textGrowth. Never clip badge text.
|
||||
- Badges/tags ("NEW", "SALE", "PRO"): only for short labels (CJK <=8 chars / Latin <=16 chars). For longer copy, use a normal text row/card instead of badge/chip style.
|
||||
- Button + icon-button row: horizontal, gap=8-12. Primary button width="fill_container"; icon-only button fixed square 44-48px.
|
||||
- Inputs: height 44px, light bg, subtle border. Use width="fill_container" in form contexts.
|
||||
- Fixed-width children must NOT exceed their parent's content area (parent width minus padding).
|
||||
|
|
@ -196,7 +196,8 @@ DESIGN GUIDELINES:
|
|||
- Icons: use "path" nodes with descriptive names matching standard icon names (e.g. "SearchIcon", "MenuIcon", "ArrowRightIcon"). The system auto-resolves names to verified SVG paths. Size 16-24px.
|
||||
- Never use emoji glyphs as icon substitutes. If an icon is needed, use a path node with a descriptive icon name.
|
||||
- Use image nodes for generic photos/illustrations only; for app preview areas prefer phone mockup placeholders
|
||||
- Phone mockup/screenshot placeholder: exactly ONE "frame" node, width 260-300, height 520-580, cornerRadius 32, solid fill matching theme + 1px subtle stroke. NEVER use ellipse or circle for mockups. NEVER add any children inside (no text, no frames, no images). All mockups must look identical.
|
||||
- Phone mockup/screenshot placeholder: exactly ONE "frame" node, width 260-300, height 520-580, cornerRadius 32, solid fill matching theme + 1px subtle stroke. NEVER use ellipse or circle for mockups. If a placeholder label is used, keep exactly ONE centered text child inside the phone frame; otherwise no children. Never put the label as a sibling below the phone.
|
||||
- Hero with phone mockup (desktop): prefer a two-column horizontal layout (left text/cta, right phone). Do NOT stack the phone below headline unless mobile.
|
||||
- NEVER use ellipse nodes for decorative/placeholder shapes. Use frame or rectangle with cornerRadius instead.
|
||||
- Avoid adding an extra full-width CTA strip directly under navigation unless the prompt explicitly asks for that section.
|
||||
- Buttons, nav items, and list items should include icons when appropriate for better UX
|
||||
|
|
@ -262,11 +263,11 @@ CRITICAL RULES:
|
|||
- Use width/height (or "fill_container") on all children. Unique descriptive IDs. All colors as fill arrays.
|
||||
- Start with <step> tags, then immediately the json block. NO preamble text.
|
||||
- After the json block, add a 1-sentence summary.
|
||||
- Phone mockup: exactly ONE "frame" node, width 260-300, height 520-580, cornerRadius 32, solid fill + 1px stroke. NEVER use ellipse. NEVER add any children inside (no text, no frames, no images). All mockups identical.
|
||||
- Phone mockup: exactly ONE "frame" node, width 260-300, height 520-580, cornerRadius 32, solid fill + 1px stroke. NEVER use ellipse. If a placeholder label is needed, allow exactly ONE centered text child inside the phone; otherwise no children. Never put placeholder text below the phone as a sibling.
|
||||
- NEVER use ellipse for decorative/placeholder shapes — use frame or rectangle with cornerRadius.
|
||||
- Navigation bars (when applicable): justifyContent="space_between", 3 groups (logo | links | CTA), padding=[0,80], alignItems="center".
|
||||
- Never use emoji as icons; use path nodes with descriptive icon names (system auto-resolves to verified SVG paths).
|
||||
- TEXT IN LAYOUTS: text inside layout frames MUST use textGrowth="fixed-width" + width="fill_container". NEVER use fixed pixel widths on text. Short labels/buttons can omit textGrowth.
|
||||
- TEXT IN LAYOUTS: vertical layout body text should use textGrowth="fixed-width" + width="fill_container". Horizontal layout labels/buttons should use textGrowth="auto" + width="fit_content" (or omit width). NEVER use fixed pixel widths on text.
|
||||
- TEXT HEIGHT: NEVER set explicit pixel height on text nodes (e.g. height:22). OMIT the height property — the engine auto-calculates from textGrowth + content. A small explicit height causes text clipping and overlap.
|
||||
- Cards with images: ALWAYS set clipContent: true + cornerRadius. Use "fill_container" width on image/body/text children inside the card.
|
||||
- CARD ROW ALIGNMENT: cards in a horizontal row MUST ALL use width="fill_container" + height="fill_container" for even distribution and equal height. Never use different fixed heights on sibling cards — it creates an ugly uneven row.
|
||||
|
|
@ -274,10 +275,10 @@ CRITICAL RULES:
|
|||
- Keep section rhythm consistent (80-120px vertical padding) and preserve alignment between sections.
|
||||
|
||||
OVERFLOW PREVENTION (CRITICAL — #1 source of visual bugs):
|
||||
- ALL text nodes inside layout frames → width="fill_container" + textGrowth="fixed-width". No exceptions. NEVER width:378 or width:224 on text inside a layout frame.
|
||||
- Do NOT force fill width for all text. Use width="fill_container" + textGrowth="fixed-width" for vertical body text; use width="fit_content"/auto for short text in horizontal rows. NEVER width:378 or width:224 on text inside a layout frame.
|
||||
- Fixed-width children must be ≤ parent content area (parent width − horizontal padding). Example: a card width=195 with padding=[24,40,24,40] has 115px available — a child with width=378 causes severe overflow.
|
||||
- CJK (Chinese/Japanese/Korean) text in buttons: each CJK char ≈ fontSize wide. "免费下载" (4 chars) at fontSize 15 = ~60px minimum content width. Button must be ≥ 60 + horizontal padding.
|
||||
- Badges with dynamic text: use width="fit_content" so the badge auto-expands to fit its text.
|
||||
- Badges must stay short-label only (CJK <=8 chars / Latin <=16 chars). For longer text, avoid badge/chip style.
|
||||
|
||||
COPYWRITING (keep all text content concise — verbose copy breaks layout and hurts aesthetics):
|
||||
- Headlines: 2-6 words, punchy. Subtitles: 1 sentence ≤15 words.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ export function prepareDesignPrompt(prompt: string): PreparedDesignPrompt {
|
|||
const sectionLines = extractWebsiteSectionLines(sections).slice(0, PROMPT_OPTIMIZER_LIMITS.maxSectionLines)
|
||||
const highlightLines = extractCoreHighlightLines(sections).slice(0, 6)
|
||||
const needsScreenshotPlaceholder = hasScreenshotRequirements(normalized)
|
||||
const needsDenseCardCompaction = hasDenseCardRequirements(normalized)
|
||||
const needsTableStructure = hasTableStructureRequirements(normalized)
|
||||
const needsHeroPhoneTwoColumn = hasHeroPhoneTwoColumnRequirements(normalized)
|
||||
|
||||
const sloganLines = extractSloganCandidates(sections).slice(0, 4)
|
||||
const personalityHint = extractProductPersonality(normalized)
|
||||
|
|
@ -99,6 +102,21 @@ export function prepareDesignPrompt(prompt: string): PreparedDesignPrompt {
|
|||
'Screenshot rule: for sections like "App截图"/"XX截图"/"screenshot", use a minimal phone placeholder (outline + short label), avoid random real screenshots, and do not recreate detailed mini-app internals.',
|
||||
)
|
||||
}
|
||||
if (needsDenseCardCompaction) {
|
||||
conciseParts.push(
|
||||
'Dense-card rule: if a horizontal row has 5+ cards (or very narrow cards), cards must be natively compact at generation time: keep max 2 text blocks per card (title + metric), rewrite copy into concise keyword phrases, remove non-essential decorative elements, and never output truncation marks ("..." / "…").',
|
||||
)
|
||||
}
|
||||
if (needsTableStructure) {
|
||||
conciseParts.push(
|
||||
'Table rule: render table as explicit header/body grid rows with aligned cells. Never collapse multiple columns into a single long text line.',
|
||||
)
|
||||
}
|
||||
if (needsHeroPhoneTwoColumn) {
|
||||
conciseParts.push(
|
||||
'Hero phone layout rule (desktop): use a horizontal two-column hero (left text/cta, right phone mockup). Do not stack the phone below headline unless mobile.',
|
||||
)
|
||||
}
|
||||
conciseParts.push(
|
||||
'Icon rule: never use emoji glyphs as icons; always use path nodes with descriptive icon names (e.g. "SearchIcon", "MenuIcon"). System auto-resolves to verified SVG paths.',
|
||||
)
|
||||
|
|
@ -280,6 +298,26 @@ function hasScreenshotRequirements(text: string): boolean {
|
|||
return /(截图|screenshot|mockup)/i.test(text)
|
||||
}
|
||||
|
||||
function hasDenseCardRequirements(text: string): boolean {
|
||||
if (/(dense|密集|多卡片|卡片过多|超过\s*4\s*个|5\+\s*cards?|cards?\s*row|一行.*卡片|横排.*卡片)/i.test(text)) {
|
||||
return true
|
||||
}
|
||||
if (/(cefr|a1[\s-]*c2|a1|a2|b1|b2|c1|c2|词库分级|分级词库|学习阶段|等级)/i.test(text)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function hasTableStructureRequirements(text: string): boolean {
|
||||
return /(table|grid|tabular|表格|表头|表体|列|行|字段|等级|级别|词汇量|适用人群|对应考试)/i.test(text)
|
||||
}
|
||||
|
||||
function hasHeroPhoneTwoColumnRequirements(text: string): boolean {
|
||||
const heroLike = /(hero|首页首屏|首屏|横幅|banner)/i.test(text)
|
||||
const phoneLike = /(phone|mockup|screenshot|截图|手机|app\s*screen|应用截图)/i.test(text)
|
||||
return heroLike && phoneLike
|
||||
}
|
||||
|
||||
function extractFallbackSectionLabels(prompt: string): string[] {
|
||||
const lines = prompt.replace(/\r/g, '').split('\n')
|
||||
const labels: string[] = []
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ const BLOCK = "```"
|
|||
export const SUB_AGENT_PROMPT = `PenNode flat JSONL engine. Output a ${BLOCK}json block with ONE node per line.
|
||||
|
||||
TYPES: frame (width,height,layout,gap,padding,justifyContent,alignItems,clipContent,cornerRadius,fill,stroke,effects,children), rectangle, ellipse, text (content,fontFamily,fontSize,fontWeight,fontStyle,fill,width,height,textAlign,textGrowth,lineHeight,letterSpacing,textAlignVertical), path (d,width,height,fill,stroke), image (src,width,height)
|
||||
textGrowth: "auto" (expand horizontally, no wrap), "fixed-width" (fixed width, height auto-sizes to wrapped content), "fixed-width-height" (both fixed). Default for text in layouts: "fixed-width" + width="fill_container".
|
||||
textGrowth: "auto" (expand horizontally, no wrap), "fixed-width" (fixed width, height auto-sizes to wrapped content), "fixed-width-height" (both fixed). Default: vertical body text uses "fixed-width" + width="fill_container"; horizontal labels use "auto" + width="fit_content" (or omit width).
|
||||
lineHeight: multiplier (1.1-1.2 for headings, 1.4-1.6 for body). letterSpacing: px (-0.5 for headlines, 0.5-2 for uppercase labels). textAlignVertical: "top"|"middle"|"bottom".
|
||||
SHARED: id, type, name, x, y, opacity
|
||||
width/height: number (px), "fill_container" (stretch), "fit_content" (shrink-wrap)
|
||||
|
|
@ -78,7 +78,7 @@ CRITICAL LAYOUT RULES (violations cause rendering bugs):
|
|||
|
||||
⚠️ TEXT RULES (the #1 most common bug source — MUST follow):
|
||||
TEXT WIDTH:
|
||||
- ALL text nodes inside a layout frame → width="fill_container" + textGrowth="fixed-width". NO EXCEPTIONS.
|
||||
- Vertical layout body text → width="fill_container" + textGrowth="fixed-width". In horizontal rows, short labels should use width="fit_content" (or omit width) + textGrowth="auto" to avoid squeezing siblings.
|
||||
- NEVER output a text node with a fixed pixel width (width:224, width:378, width:784 etc.) inside a layout frame. This causes the text to overflow horizontally and break the design.
|
||||
- The ONLY time text can have a fixed pixel width is when it is NOT inside a layout frame (layout="none" parent).
|
||||
- BAD example (causes overflow): parent card width=195, padding=[24,40,24,40] (available=115px), child text width=378 → text overflows by 263px!
|
||||
|
|
@ -128,15 +128,17 @@ DESIGN RULES:
|
|||
- CJK FONTS: When content is in Chinese/Japanese/Korean, use CJK-compatible fonts — "Noto Sans SC" for headings, "Inter" or "Noto Sans SC" for body. NEVER use "Space Grotesk" or "Manrope" for CJK text (they have no CJK glyphs). CJK lineHeight: 1.3-1.4 for headings, 1.6-1.8 for body. CJK letterSpacing: 0 for body, never negative.
|
||||
- Cards in horizontal rows: ALL cards MUST use width="fill_container" + height="fill_container" for even distribution and equal height. Never use fixed pixel width/height on cards in a row. A card with icon+title+description needs at least 160-200px content height — the row will auto-size. ALWAYS set clipContent: true on cards with cornerRadius + image children.
|
||||
- Dense rows (5+ cards): compact card internals aggressively. Use very short titles (CJK ≤6 chars / Latin ≤12 chars), keep max 2 text blocks per card (title + short metric), and remove non-essential decorative elements. Refine text into short keyword phrases; never use "..." or "…" truncation.
|
||||
- Dense rows must be natively compact in the generated JSON. Do NOT emit long card descriptions expecting post-processing cleanup.
|
||||
- Icons: "path" nodes with descriptive names (e.g. "SearchIcon", "MenuIcon", "ArrowRightIcon", "StarIcon", "ShieldIcon", "ZapIcon"). System auto-resolves to verified SVG paths. Size 16-24px. NEVER use emoji.
|
||||
- PHONE MOCKUP: exactly ONE "frame" node, width 260-300, height 520-580, cornerRadius 32, solid fill + 1px stroke. NEVER use ellipse or circle for mockups. NEVER add any children inside (no text, no frames, no images). Every phone mockup must look identical.
|
||||
- PHONE MOCKUP: exactly ONE "frame" node, width 260-300, height 520-580, cornerRadius 32, solid fill + 1px stroke. NEVER use ellipse or circle for mockups. If placeholder copy is needed, keep exactly ONE centered text child inside the phone; otherwise no children. Never place that label as a sibling below the phone.
|
||||
- NEVER use ellipse nodes for decorative shapes. Use frame or rectangle with cornerRadius instead.
|
||||
- Use STYLE GUIDE colors/fonts from user prompt consistently. Do not introduce random colors.
|
||||
- TEXT: text inside layout frames MUST use textGrowth="fixed-width" + width="fill_container". NEVER set fixed pixel width on text. NEVER set height on text — omit it entirely, the engine auto-sizes. Short labels/buttons can omit textGrowth.
|
||||
- TEXT: for vertical layouts, body text should use textGrowth="fixed-width" + width="fill_container". For horizontal rows (nav/footer/button rows), labels should use textGrowth="auto" + width="fit_content" (or omit width). NEVER set fixed pixel width on text. NEVER set height on text — omit it entirely; the engine auto-sizes.
|
||||
- BUTTONS: height 44-52px, padding [12, 24] minimum. With icon+text: layout="horizontal", gap=8, alignItems="center". Sizing: "fill_container" (stretch), "fit_content" (hug content), or fixed px — choose per context.
|
||||
- CJK BUTTONS (Chinese/Japanese/Korean text): each CJK character renders ~1.0× fontSize wide. For "免费下载" (4 chars) at fontSize 15: content needs ~60px width → button needs 60 + horizontal padding (e.g. padding [8,22] = 44px → total 104px minimum). ALWAYS calculate: button width ≥ charCount × fontSize + totalHorizontalPadding.
|
||||
- ICON-ONLY BUTTONS (heart, bookmark, share, etc.): square frame, minimum 44x44px, justifyContent="center", alignItems="center". Path icon inside 20-24px.
|
||||
- BADGES/TAGS (e.g. "NEW", "SALE", "PRO"): frame with padding [4, 12] minimum, cornerRadius 4-6, height="fit_content". For badges with CJK or long text, use width="fit_content" so badge auto-sizes. Text inside must NOT be clipped — use short fontSize (11-13px), no textGrowth needed.
|
||||
- BADGES/TAGS (e.g. "NEW", "SALE", "PRO"): only for SHORT labels (CJK <=8 chars / Latin <=16 chars). If text is longer, do NOT use badge/chip style — use a normal text row or small card instead.
|
||||
- HERO + PHONE LAYOUT (desktop): when hero contains a phone mockup, prefer a two-column horizontal layout (left text/cta, right phone). Do NOT stack phone below headline unless mobile.
|
||||
- BUTTON + ICON-BUTTON ROW: horizontal frame, gap=8-12. Primary button width="fill_container" to take remaining space; icon-only button fixed square (44-48px).
|
||||
- LANDING PAGE SECTIONS (only when designing landing pages/websites):
|
||||
- Hero: large headline (40-56px), gradient or bold backgrounds, clear CTA, generous whitespace
|
||||
|
|
|
|||
|
|
@ -445,6 +445,7 @@ async function executeSubAgent(
|
|||
subtask,
|
||||
plan,
|
||||
promptOverride ?? preparedPrompt.subAgentPrompt,
|
||||
request.prompt,
|
||||
request.context?.variables,
|
||||
request.context?.themes,
|
||||
)
|
||||
|
|
@ -627,6 +628,7 @@ function buildSubAgentUserPrompt(
|
|||
subtask: SubTask,
|
||||
plan: OrchestratorPlan,
|
||||
compactPrompt: string,
|
||||
fullPrompt: string,
|
||||
variables?: Record<string, VariableDefinition>,
|
||||
themes?: Record<string, string[]>,
|
||||
): string {
|
||||
|
|
@ -647,10 +649,33 @@ CRITICAL LAYOUT CONSTRAINTS:
|
|||
- Use "fill_container" for children that stretch, "fit_content" for shrink-wrap sizing.
|
||||
- Use justifyContent="space_between" to distribute items (e.g. navbar: logo | links | CTA). Use padding=[0,80] for horizontal page margins.
|
||||
- For side-by-side layouts, nest a horizontal frame with child frames using "fill_container" width.
|
||||
- Phone mockup = ONE frame node, cornerRadius 32, NO children inside. NEVER use ellipse.
|
||||
- Phone mockup = ONE frame node, cornerRadius 32. If a placeholder label is needed, allow exactly ONE centered text child inside the phone; otherwise no children. Never place placeholder text below the phone as a sibling. NEVER use ellipse.
|
||||
- Text height must fit content: estimate lines × fontSize × 1.4.
|
||||
- IDs prefix="${subtask.idPrefix}-". No <step> tags. Output \`\`\`json immediately.`
|
||||
|
||||
if (needsNativeDenseCardInstruction(subtask.label, compactPrompt, fullPrompt)) {
|
||||
prompt += `\n\nNATIVE DENSE-CARD MODE (must be solved during generation):
|
||||
- If you create a horizontal row with 5+ cards (or cards become narrow), compact each card natively BEFORE output.
|
||||
- Each card: max 2 text blocks only (title + one short metric). Remove long descriptions.
|
||||
- Rewrite long copy into concise keyword phrases. Never use truncation marks ("..." or "…").
|
||||
- Prefer removing non-essential decorative elements before shrinking readability.
|
||||
- Do NOT rely on post-processing to prune card content.`
|
||||
}
|
||||
if (needsTableStructureInstruction(subtask.label, compactPrompt, fullPrompt)) {
|
||||
prompt += `\n\nTABLE MODE (must be structured natively):
|
||||
- Build table as explicit grid frames, NOT a single long text line.
|
||||
- Header must be its own horizontal row with separate cell frames for each column.
|
||||
- Body rows must align to the same column structure as header.
|
||||
- Keep level badge/chip inside the level cell; do not merge multiple columns into one text node.
|
||||
- In table rows, avoid badge/button auto-style patterns unless the node is explicitly a chip.`
|
||||
}
|
||||
if (needsHeroPhoneTwoColumnInstruction(subtask.label, compactPrompt, fullPrompt)) {
|
||||
prompt += `\n\nHERO PHONE LAYOUT MODE (desktop):
|
||||
- Use a horizontal two-column hero layout: left = headline/subtitle/CTA, right = phone mockup.
|
||||
- Keep phone as a sibling in the same horizontal row, NOT stacked below the headline.
|
||||
- Only use stacked layout for mobile/narrow viewport sections.`
|
||||
}
|
||||
|
||||
// Inject style guide so sub-agent uses consistent colors/fonts
|
||||
if (plan.styleGuide) {
|
||||
const sg = plan.styleGuide
|
||||
|
|
@ -671,6 +696,50 @@ CRITICAL LAYOUT CONSTRAINTS:
|
|||
return prompt
|
||||
}
|
||||
|
||||
function needsNativeDenseCardInstruction(
|
||||
subtaskLabel: string,
|
||||
compactPrompt: string,
|
||||
fullPrompt: string,
|
||||
): boolean {
|
||||
const text = `${subtaskLabel}\n${compactPrompt}\n${fullPrompt}`.toLowerCase()
|
||||
if (/(dense|密集|多卡片|卡片过多|超过\s*4\s*个|5\+\s*cards?|cards?\s*row|一行.*卡片|横排.*卡片)/.test(text)) {
|
||||
return true
|
||||
}
|
||||
if (/(cefr|a1[\s-]*c2|a1|a2|b1|b2|c1|c2|词库分级|分级词库|学习阶段|等级)/.test(text)) {
|
||||
return true
|
||||
}
|
||||
if (/(feature\s*cards?|cards?\s*section|词库|词汇|card)/.test(text) && /(a1|b1|c1|c2|cefr|等级|阶段)/.test(text)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function needsTableStructureInstruction(
|
||||
subtaskLabel: string,
|
||||
compactPrompt: string,
|
||||
fullPrompt: string,
|
||||
): boolean {
|
||||
const text = `${subtaskLabel}\n${compactPrompt}\n${fullPrompt}`.toLowerCase()
|
||||
if (/(table|grid|tabular|表格|表头|表体|列|行|字段|等级|级别|词汇量|适用人群|对应考试)/.test(text)) {
|
||||
return true
|
||||
}
|
||||
if (/(cefr|a1[\s-]*c2|a1|a2|b1|b2|c1|c2)/.test(text) && /(level|table|表格|等级)/.test(text)) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function needsHeroPhoneTwoColumnInstruction(
|
||||
subtaskLabel: string,
|
||||
compactPrompt: string,
|
||||
fullPrompt: string,
|
||||
): boolean {
|
||||
const text = `${subtaskLabel}\n${compactPrompt}\n${fullPrompt}`.toLowerCase()
|
||||
const heroLike = /(hero|首页首屏|首屏|横幅|banner)/.test(text)
|
||||
const phoneLike = /(phone|mockup|screenshot|截图|手机|app\s*screen|应用截图)/.test(text)
|
||||
return heroLike && phoneLike
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ID namespace isolation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Reference in a new issue