fix(agent): align mobile rail guidance
This commit is contained in:
parent
364c670ac5
commit
ee25b87d16
|
|
@ -11,8 +11,9 @@ category: domain
|
|||
|
||||
MOBILE APP — MANDATORY THREE-SECTION ARCHITECTURE:
|
||||
|
||||
Every mobile screen is composed as a vertical stack of exactly three sections.
|
||||
You MUST define all three before generating any content.
|
||||
Every mobile screen accounts for three logical layers: status chrome, app content,
|
||||
and optional bottom navigation. These are architectural layers, not a requirement
|
||||
to wrap every content section inside one padded App Content frame.
|
||||
|
||||
Screen-height contract: use numeric 390-393×844 as a temporary construction seed so an empty skeleton is visible. Before finishing, a normal content-driven mobile page switches its root to `height="fit_content"` (Hug), matching its completed flow. Keep a numeric viewport only when the user explicitly requested that viewport/device frame or the design deliberately contains one clipped viewport body that must consume remaining height.
|
||||
|
||||
|
|
@ -28,17 +29,25 @@ The status bar (time, signal, wifi, battery) is **automatically pre-inserted** b
|
|||
|
||||
Chip rows (filter/date/guests pills): each chip HUGS (width fit_content, single-line text, height 36-44, cornerRadius=full); the ROW clips overflow (clipContent) instead of squeezing chips — never let a pill's text wrap. A badge/pill/button frame ALWAYS carries its content (text or icon) — an empty decorated frame renders as a mystery blob.
|
||||
|
||||
ALL content elements must sit inside ONE wrapper container (vertical stack).
|
||||
Keep the mobile root at 0 horizontal padding so status chrome, integrated bottom
|
||||
navigation, and intentional full-bleed media can remain full width. Emit ordinary
|
||||
app content as transparent root-direct section frames with
|
||||
`width="fill_container"`, `height="fit_content"`, `layout="vertical"`, and the
|
||||
same `padding: [0,24]` rail exactly once per section. Do not repeat that
|
||||
horizontal inset on child wrappers. Only a deliberately clipped scroll viewport
|
||||
under an explicit fixed-height root may use `height="fill_container"`; in that
|
||||
case it is the ONE named remainder consumer, not a sizing mode copied onto its
|
||||
sections.
|
||||
|
||||
The ordinary content wrapper is `width="fill_container"`, `height="fit_content"`, `layout="vertical"`. Its child sections/cards also Hug Height. Only a deliberately clipped scroll viewport under an explicit fixed-height root may use `height="fill_container"`; in that case it is the ONE named remainder consumer, not a sizing mode copied onto its sections.
|
||||
Use gap and vertical padding, not margins or empty spacers, for rhythm between
|
||||
sections. Give the last ordinary content section enough bottom padding to clear
|
||||
the following bottom navigation.
|
||||
|
||||
Wrapper provides:
|
||||
A clipped horizontal scroller is the rail exception: its section stays full
|
||||
width, its section header gets 24px left/right padding, and its clipped
|
||||
viewport gets a 24px leading inset with a flush 0px trailing edge.
|
||||
|
||||
- Consistent left/right padding: 16-28px (applied ONCE at wrapper level; reference-measured screens use [0,24] on the wrapper with a 402px root)
|
||||
- Gap-based vertical spacing between sections (use gap, NOT margins): 24-40 by density
|
||||
- padding-bottom equal to the gap value for bottom space (NOT spacer elements)
|
||||
|
||||
Content stacking order inside the wrapper:
|
||||
Content stacking order across the ordinary sections:
|
||||
|
||||
1. Top context: title / navigation header / search / filters
|
||||
2. Primary content: the main "job to be done" for this screen
|
||||
|
|
@ -66,7 +75,8 @@ Rules:
|
|||
|
||||
DO NOT:
|
||||
|
||||
- Add per-section horizontal padding (wrapper handles it)
|
||||
- Put the 24px content rail on the root page or duplicate it on an inner wrapper
|
||||
- Let an ordinary root-direct content section touch the screen edge
|
||||
- Use spacer elements for bottom space (use padding-bottom)
|
||||
- Cram multiple competing sections above the fold
|
||||
|
||||
|
|
|
|||
|
|
@ -21,4 +21,4 @@ DESIGN CRAFT:
|
|||
- Cards: consistent cornerRadius/padding/shadow. Content: image - title - description - action.
|
||||
- Nav: logo + 3-5 links + CTA. space_between distribution. Keep minimal.
|
||||
- Alternate section backgrounds (white/#F8FAFC) for natural separation.
|
||||
- Mobile: one App Content wrapper owns horizontal padding and vertical gaps; inner sections should not each add competing gutters.
|
||||
- Mobile: keep the page root at 0 horizontal padding for full-width chrome/full-bleed media; ordinary transparent root-direct content sections each own the same 24px rail exactly once. A clipped horizontal scroller stays full width, with a 24px-inset header and a 24px leading/0px trailing viewport inset.
|
||||
|
|
|
|||
|
|
@ -138,9 +138,11 @@ AESTHETIC HYGIENE — keep these silent (never emit, the post-pass also strips t
|
|||
- INNER LAYOUT FRAMES (sections, wrappers, header / body containers inside a card) DO NOT need
|
||||
fill, stroke, OR shadow. They inherit from the page / card surface. Only opt into a fill /
|
||||
border / shadow on the OUTER card, button, badge, chip — NEVER on the wrapper that holds it.
|
||||
- ONE PAGE GUTTER, ON THE ROOT. The root frame carries the horizontal gutter (e.g.
|
||||
`padding: [0,20]`); EVERY content section uses horizontal padding 0 and only sets vertical
|
||||
padding. This is what keeps every section's left edge aligned — if sections each set their own
|
||||
h-padding (one 20, one 16, one 0) their content no longer lines up. Hero / banner / image-bleed
|
||||
sections sit edge-to-edge by simply NOT adding horizontal padding (the root gutter shows
|
||||
through). Never stack both (root gutter + per-section h-padding = a doubled inset).
|
||||
- MOBILE CONTENT RAIL LIVES ON ORDINARY SECTIONS, NOT THE PAGE ROOT. A mobile root may keep
|
||||
horizontal padding 0 so the status bar, integrated bottom navigation, and intentional
|
||||
full-bleed media can stay full width. Every ordinary transparent root-direct content section
|
||||
owns the same 24px left/right rail exactly once (`padding: [0,24]`); do not repeat that inset
|
||||
on an inner wrapper. A clipped horizontal scroller is the exception: keep its section/viewport
|
||||
full width, inset its header 24px on both sides, and give the clipped viewport a 24px leading
|
||||
inset with a flush 0px trailing edge. Never stack root + section or section + inner-wrapper
|
||||
horizontal padding.
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@ NO PHONE MOCKUP WRAPPER: The whole design IS a mobile screen. Do NOT wrap your s
|
|||
|
||||
MOBILE WIDTH SAFETY: Every visible child must stay inside the 390px screen width. Do not create horizontal rows, chips, cards, or buttons that overflow outside the root; wrap, shrink, or clip horizontal lists instead.
|
||||
|
||||
MOBILE SINGLE CONTENT RAIL: All non-chrome sections must align to the same 24px left/right content rail. Section roots should stay transparent with width="fill_container" and height="fit_content"; apply padding once at the section root or first content wrapper. Do not create full-width colored wrapper surfaces just to hold content.
|
||||
MOBILE SINGLE CONTENT RAIL: The root page may keep 0 horizontal padding so the pre-inserted status bar, integrated bottom navigation, and intentional full-bleed media remain full width. Every ordinary transparent root-direct content section must own the same 24px left/right rail exactly once (`padding: [0,24]`) with width="fill_container" and height="fit_content". Do not repeat that inset on an inner wrapper, and do not create full-width colored wrapper surfaces just to hold content.
|
||||
|
||||
MOBILE SCROLLER RAIL: A clipped horizontal scroller is the exception to section padding. Keep the scroller section full width, inset its header 24px on both sides, and give the clipped viewport a 24px leading inset with a flush 0px trailing edge so its first item aligns to the content rail while the next item can clip.
|
||||
|
||||
{{mobileRhythm}}
|
||||
|
||||
|
|
|
|||
|
|
@ -294,9 +294,10 @@ mod tests {
|
|||
"mobile-app domain skill must resolve for a mobile ask"
|
||||
);
|
||||
assert!(
|
||||
prompt.contains("ordinary content wrapper")
|
||||
&& prompt.contains("height=\"fit_content\""),
|
||||
"mobile domain must teach Hug Height as the ordinary content default"
|
||||
prompt.contains("transparent root-direct section frames")
|
||||
&& prompt.contains("height=\"fit_content\"")
|
||||
&& prompt.contains("padding: [0,24]"),
|
||||
"mobile domain must teach Hug Height and the section-owned content rail"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,15 @@ mod tests {
|
|||
assert!(skill.content.contains("Mobile top rhythm"));
|
||||
assert!(skill.content.contains("Do not force bottom navigation"));
|
||||
assert!(skill.content.contains("Not a floating pill"));
|
||||
assert!(skill
|
||||
.content
|
||||
.contains("transparent root-direct section frames"));
|
||||
assert!(skill.content.contains("padding: [0,24]"));
|
||||
assert!(skill.content.contains("24px leading inset"));
|
||||
assert!(skill.content.contains("0px trailing"));
|
||||
assert!(!skill
|
||||
.content
|
||||
.contains("ALL content elements must sit inside ONE wrapper"));
|
||||
assert!(skill
|
||||
.content
|
||||
.contains("Do not repeat the same predictable mobile stack"));
|
||||
|
|
|
|||
|
|
@ -1037,6 +1037,11 @@ fn build_subagent_prompt_core(
|
|||
.as_ref()
|
||||
.map(|instruction| format!("{instruction}\n\n"))
|
||||
.unwrap_or_default();
|
||||
let spacing_rule = if is_mobile_layout {
|
||||
"SPACING CONSISTENCY — MOBILE CONTENT RAIL: The root page may keep 0 horizontal padding for full-width status/navigation/full-bleed media. This ordinary transparent root-direct section owns padding:[0,24] exactly once; do not duplicate it on an inner wrapper. If this section is a clipped horizontal scroller, keep its section full width, inset its header 24px on both sides, and give the clipped viewport a 24px leading inset with a flush 0px trailing edge."
|
||||
} else {
|
||||
"SPACING CONSISTENCY: Use a single outer content gutter and consistent internal gaps. Do not create nested wrappers with conflicting padding or content touching edges."
|
||||
};
|
||||
|
||||
// Two constraints differ by output protocol. The public subagent path uses
|
||||
// the script-gen branch; the raw-JSONL branch is legacy-only for direct
|
||||
|
|
@ -1064,7 +1069,7 @@ CRITICAL LAYOUT CONSTRAINTS:\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\
|
||||
- {spacing_rule}\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\
|
||||
|
|
|
|||
|
|
@ -219,7 +219,7 @@ fn subagent_prompt_carries_ts_layout_contract() {
|
|||
false,
|
||||
);
|
||||
|
||||
let required = "Page sections:|Food Categories [category chips]|\"fill_container\"|\"fit_content\"|Generate enough elements|MOBILE STATUS BAR|time, signal, wifi, battery|NO PHONE MOCKUP WRAPPER|MOBILE WIDTH SAFETY|MOBILE SINGLE CONTENT RAIL|MOBILE SEARCH BAR|MOBILE SECTION CHROME|MOBILE VERTICAL RHYTHM|MOBILE TOP RHYTHM|MOBILE GRID ALIGNMENT|MOBILE CARD OVERLAYS|MOBILE IMAGE PRESENTATION|verify only rendering integrity|Do not judge or replace a displayed image during self-check based on subject relevance|explicit user-requested image edit remains allowed|NO BLANK PLACEHOLDERS|MOBILE NAV SURFACE|MOBILE NAV SHADOW|NO FIXED FOOD TEMPLATE|Do not default to the same search + categories + orange promo + two product cards composition|TYPOGRAPHY HIERARCHY|DENSITY|VISUAL HIERARCHY|SPACING CONSISTENCY|CRAFT POLISH|MEDIA CONSISTENCY|ICON SCALE|SIGNATURE MOMENT|WOW FACTOR|COMPOSITIONAL CONTRAST|PREMIUM DETAIL|NO DECORATION SPAM";
|
||||
let required = "Page sections:|Food Categories [category chips]|\"fill_container\"|\"fit_content\"|Generate enough elements|MOBILE STATUS BAR|time, signal, wifi, battery|NO PHONE MOCKUP WRAPPER|MOBILE WIDTH SAFETY|MOBILE SINGLE CONTENT RAIL|MOBILE SCROLLER RAIL|MOBILE SEARCH BAR|MOBILE SECTION CHROME|MOBILE VERTICAL RHYTHM|MOBILE TOP RHYTHM|MOBILE GRID ALIGNMENT|MOBILE CARD OVERLAYS|MOBILE IMAGE PRESENTATION|verify only rendering integrity|Do not judge or replace a displayed image during self-check based on subject relevance|explicit user-requested image edit remains allowed|NO BLANK PLACEHOLDERS|MOBILE NAV SURFACE|MOBILE NAV SHADOW|NO FIXED FOOD TEMPLATE|Do not default to the same search + categories + orange promo + two product cards composition|TYPOGRAPHY HIERARCHY|DENSITY|VISUAL HIERARCHY|SPACING CONSISTENCY|CRAFT POLISH|MEDIA CONSISTENCY|ICON SCALE|SIGNATURE MOMENT|WOW FACTOR|COMPOSITIONAL CONTRAST|PREMIUM DETAIL|NO DECORATION SPAM";
|
||||
// Mobile UI guardrails now load via the `mobile-ui` skill (system prompt);
|
||||
// section + quality markers stay in the user prompt. Accept either. The
|
||||
// `"fill_container"` / `"fit_content"` markers are quote-only (no
|
||||
|
|
@ -234,6 +234,30 @@ fn subagent_prompt_carries_ts_layout_contract() {
|
|||
"missing {required}"
|
||||
);
|
||||
}
|
||||
let combined = format!("{}\n{}", cr.system_prompt, cr.user_prompt);
|
||||
for required in [
|
||||
"root page may keep 0 horizontal padding",
|
||||
"ordinary transparent root-direct content section",
|
||||
"padding:[0,24]",
|
||||
"inset its header 24px on both sides",
|
||||
"24px leading inset",
|
||||
"0px trailing edge",
|
||||
] {
|
||||
assert!(
|
||||
combined.contains(required),
|
||||
"missing mobile content-rail rule {required}"
|
||||
);
|
||||
}
|
||||
for stale in [
|
||||
"ONE PAGE GUTTER, ON THE ROOT",
|
||||
"ALL content elements must sit inside ONE wrapper",
|
||||
"Add per-section horizontal padding (wrapper handles it)",
|
||||
] {
|
||||
assert!(
|
||||
!combined.contains(stale),
|
||||
"stale mobile gutter rule must not survive: {stale}"
|
||||
);
|
||||
}
|
||||
assert!(!cr.system_prompt.contains("MOBILE IMAGE QUALITY"));
|
||||
assert!(!cr.user_prompt.contains("MOBILE IMAGE QUALITY"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue