fix(orchestrator): refine chat-generated mobile layouts

This commit is contained in:
Kayshen-X 2026-06-21 18:24:12 +08:00
parent 7826163240
commit efb4b84e7d
3 changed files with 29 additions and 3 deletions

View file

@ -20,6 +20,7 @@ pub struct NormInfo {
/// 移动端宽度上限(含)—— ≤ 此值视为移动端单屏。
const MOBILE_MAX_WIDTH: f64 = 480.0;
const MOBILE_DEFAULT_HEIGHT: f64 = 812.0;
const MOBILE_DEFAULT_ROOT_GAP: f64 = 20.0;
/// subtask 的 id / label 命中即视为"状态栏"区块 —— 移动端由
/// scaffold 注入固定状态栏,plan 里若带状态栏 subtask 则剔除。
@ -124,7 +125,9 @@ pub fn normalize(plan: &mut OrchestratorPlan, req: &DesignRequest) -> NormInfo {
if is_mobile {
plan.root_frame.layout = Some("vertical".into());
plan.root_frame.gap = Some(0.0);
if plan.root_frame.gap.unwrap_or(0.0) <= 0.0 {
plan.root_frame.gap = Some(MOBILE_DEFAULT_ROOT_GAP);
}
plan.root_frame.padding = Some(0.0);
if plan.root_frame.height <= 0.0 {
plan.root_frame.height = MOBILE_DEFAULT_HEIGHT;
@ -249,7 +252,7 @@ mod tests {
}
#[test]
fn normalize_mobile_forces_vertical_root_layout() {
fn normalize_mobile_forces_vertical_root_layout_and_keeps_positive_gap() {
let mut p = plan(390.0, vec![subtask("hero", "Hero")]);
p.root_frame.layout = Some("none".into());
p.root_frame.gap = Some(12.0);
@ -258,10 +261,20 @@ mod tests {
normalize(&mut p, &req());
assert_eq!(p.root_frame.layout.as_deref(), Some("vertical"));
assert_eq!(p.root_frame.gap, Some(0.0));
assert_eq!(p.root_frame.gap, Some(12.0));
assert_eq!(p.root_frame.padding, Some(0.0));
}
#[test]
fn normalize_mobile_zero_gap_uses_section_spacing() {
let mut p = plan(390.0, vec![subtask("hero", "Hero")]);
p.root_frame.gap = Some(0.0);
normalize(&mut p, &req());
assert_eq!(p.root_frame.gap, Some(20.0));
}
#[test]
fn normalize_mobile_zero_height_uses_default_viewport() {
let mut p = plan(390.0, vec![subtask("hero", "Hero")]);

View file

@ -535,6 +535,13 @@ Overall design: {}\n\n\
CRITICAL LAYOUT CONSTRAINTS:\n\
- {}\n\
- Target content amount: ~{:.0}px tall. Generate enough elements to fill this area.\n\
- DENSITY: Do NOT pack the area edge-to-edge. Prefer fewer, stronger modules with visible negative space; most sections should have 3-5 primary rows/cards at most.\n\
- VISUAL HIERARCHY: Each section must have one clear focal element, secondary supporting text, and quieter metadata. Avoid equal-weight blocks competing for attention.\n\
- SPACING CONSISTENCY: Use a single outer content gutter and consistent internal gaps. Do not create nested wrappers with conflicting padding or content touching edges.\n\
- CRAFT POLISH: Add refinement through restrained 1px low-contrast borders, tonal surfaces, small state badges, and subtle shadows. Avoid template-like thick outlines, giant pills, or flat blocks with no micro-detail.\n\
- MEDIA CONSISTENCY: Use photographic images sparingly and keep them visually consistent in subject, crop, tone, and radius. For food/category UI, prefer cohesive icon or illustration tiles over random unrelated photos.\n\
- ICON SCALE: Icons support content; keep most icons 16-22px inside 36-48px controls. Avoid oversized circular icon bubbles or repeated identical icon treatments unless the design brief calls for them.\n\
- ACCENT DISCIPLINE: Reserve saturated accent color for one primary CTA or promo plus small highlights. Do not apply it to every icon, label, border, and large surface at once.\n\
- {}\n\
- NEVER set x or y on children inside layout frames.\n\
- Use \"fill_container\" for children that stretch, \"fit_content\" for shrink-wrap sizing.\n\

View file

@ -160,6 +160,12 @@ fn subagent_prompt_carries_ts_layout_contract() {
assert!(cr.user_prompt.contains("NO BLANK PLACEHOLDERS"));
assert!(cr.user_prompt.contains("MOBILE NAV SURFACE"));
assert!(cr.user_prompt.contains("TYPOGRAPHY HIERARCHY"));
assert!(cr.user_prompt.contains("DENSITY"));
assert!(cr.user_prompt.contains("VISUAL HIERARCHY"));
assert!(cr.user_prompt.contains("SPACING CONSISTENCY"));
assert!(cr.user_prompt.contains("CRAFT POLISH"));
assert!(cr.user_prompt.contains("MEDIA CONSISTENCY"));
assert!(cr.user_prompt.contains("ICON SCALE"));
}
#[test]