fix(ai): edge-padding detector skips when any section has its own h-padding
User-reported 2026-05-11 mobile food design — the page had Header (search bar + cart), Categories (icon row), and Bottom Nav each carrying their own horizontal padding by design, but Hero section left its frame edge-to-edge intentionally. Previous version saw Hero's missing padding + ≥1 offending child and flagged → root got +16px gutter on top of every per-section-padded sibling, producing a visible double-inset / "边距过大" complaint. Treat any non-fullbleed content child carrying its own h-padding as a signal that the design has chosen the per-section gutter mode. Once that signal is observed, skip the root-level recommendation entirely so we don't double up. Hero / banner / image-bleed roles remain filtered out of the signal pass via FULL_BLEED_ROLES so a hero with no padding still doesn't activate the detector. Test: covers the user's exact pattern (categories + content with per-section padding + hero without) — previous expectation flipped from "fire" to "do not fire".
This commit is contained in:
parent
e2aff6c542
commit
f93a9437aa
|
|
@ -131,6 +131,27 @@ describe('detectEdgeSectionPadding', () => {
|
|||
expect(detectEdgeSectionPadding(root)).toHaveLength(0);
|
||||
});
|
||||
|
||||
// 2026-05-11 user-reported "Today's Special Deal" mobile design — Header,
|
||||
// Categories, and Bottom Nav each carried their own horizontal padding,
|
||||
// but Hero (the Today's Special card) did not. Previous version saw
|
||||
// Hero's missing padding + ≥1 offending child and flagged → root got
|
||||
// 16px gutter on top of every per-section-padded sibling, doubling the
|
||||
// inset everywhere except Hero. New rule: if ANY non-fullbleed content
|
||||
// child already has its own padding, the design has chosen the per-
|
||||
// section gutter mode and root MUST NOT be flagged.
|
||||
it('does NOT flag when ANY content section already has its own h-padding (per-section mode)', () => {
|
||||
const root = mobileRoot([
|
||||
section('hero', [text('hero-text')]), // no padding — would have flagged alone
|
||||
section('categories', [icon('i1'), icon('i2')], { padding: [12, 16] }), // per-section
|
||||
section('content', [text('t')], { padding: [0, 24] }), // per-section
|
||||
]);
|
||||
|
||||
// Categories + content set per-section gutters; Hero is left edge-to-
|
||||
// edge by design (banner-style fits even without `role: hero`). Root
|
||||
// should stay 0 — flagging would double-pad categories/content.
|
||||
expect(detectEdgeSectionPadding(root)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('preserves vertical padding when suggesting the fix', () => {
|
||||
const root = mobileRoot(
|
||||
[section('a', [text('t1')]), section('b', [text('t2')])],
|
||||
|
|
|
|||
|
|
@ -133,17 +133,39 @@ export function detectEdgeSectionPadding(root: PenNode): Issue[] {
|
|||
if (isMobileRoot) {
|
||||
const rootPadL = getPaddingLeft(node);
|
||||
if (rootPadL === 0) {
|
||||
// Two-pass scan over content children (skipping full-bleed
|
||||
// chrome / image-only banners which are intentionally edge-to-
|
||||
// edge):
|
||||
// - any with own h-padding > 0 → design has chosen the
|
||||
// per-section gutter pattern,
|
||||
// adding root padding would
|
||||
// double-inset everything else
|
||||
// - any with no padding but text/icon descendants → would
|
||||
// genuinely glue content to
|
||||
// the viewport edge, so flag
|
||||
//
|
||||
// 2026-05-11 user feedback: previous version flagged whenever
|
||||
// ≥1 child had no padding, even when other siblings already
|
||||
// carried per-section padding. Result: root got extra 16px on
|
||||
// top of the existing per-section gutters → "边距过大". The new
|
||||
// rule treats any per-section-padded sibling as a signal that
|
||||
// the design has opted out of root-level gutter and aborts the
|
||||
// detector entirely.
|
||||
let hasPerSectionPaddedSibling = false;
|
||||
const offendingChildren: PenNode[] = [];
|
||||
for (const child of (node as { children: PenNode[] }).children) {
|
||||
if (child.type !== 'frame') continue;
|
||||
const role = ((child as { role?: string }).role ?? '').toLowerCase();
|
||||
if (FULL_BLEED_ROLES.has(role)) continue;
|
||||
if (isImageOnlySection(child)) continue;
|
||||
if (getPaddingLeft(child) > 0) continue;
|
||||
if (getPaddingLeft(child) > 0) {
|
||||
hasPerSectionPaddedSibling = true;
|
||||
continue;
|
||||
}
|
||||
if (!hasTextOrIconDescendant(child)) continue;
|
||||
offendingChildren.push(child);
|
||||
}
|
||||
if (offendingChildren.length > 0) {
|
||||
if (offendingChildren.length > 0 && !hasPerSectionPaddedSibling) {
|
||||
const currentPad = (node as unknown as { padding?: unknown }).padding;
|
||||
let suggested: number[];
|
||||
if (Array.isArray(currentPad) && currentPad.length === 4) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue