Skip to content

fix: add layout rule exceptions for icons, backgrounds, images, text#100

Merged
let-sunny merged 21 commits intomainfrom
claude/icon-layout-exceptions-bYoVh
Mar 26, 2026
Merged

fix: add layout rule exceptions for icons, backgrounds, images, text#100
let-sunny merged 21 commits intomainfrom
claude/icon-layout-exceptions-bYoVh

Conversation

@let-sunny
Copy link
Copy Markdown
Owner

@let-sunny let-sunny commented Mar 26, 2026

Summary

  • no-auto-layout: 비주얼 리프 자식만 있는 프레임(아이콘 등), INSTANCE 노드 예외 처리
  • absolute-position-in-auto-layout: 풀사이즈 배경 요소(부모 대비 90%+) 예외 추가
  • missing-size-constraint: maxWidth만 강제, minWidth 제거. TEXT 노드/유일 FILL 자식/부모 maxWidth/루트 레벨/그리드/flex wrap 예외 추가
  • fixed-size-in-auto-layout: 이미지 fills 있는 노드(썸네일/아바타) 예외 추가
  • 모든 예외 로직을 rule-exceptions.ts로 분리하여 관리 용이하게 리팩토링

Test plan

  • no-auto-layout 테스트 15개 통과
  • responsive-fields 테스트 11개 통과
  • rule-exceptions 전용 테스트 8개 통과
  • structure 전체 43개 + exceptions 8개 = 53개 통과

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced advanced node classification system to refine design rule evaluation.
  • Bug Fixes

    • Improved auto-layout, absolute positioning, and size constraint rules with enhanced exemption logic.
    • Better recognition of visual-only elements and layout contexts in rule evaluation.
  • Tests

    • Expanded test coverage for rule evaluation and node classification scenarios.

claude added 7 commits March 26, 2026 07:06
Frames whose children are all vector/shape types (VECTOR,
BOOLEAN_OPERATION, ELLIPSE, LINE, STAR, REGULAR_POLYGON, RECTANGLE)
are treated as icon-like and excluded from the no-auto-layout rule.
Auto-layout is meaningless for these frames since they contain only
visual elements, not layout elements.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Change criteria from "all children are vector/shape" to "exactly one
child that is a visual leaf type". A frame wrapping a single VECTOR,
BOOLEAN_OPERATION, ELLIPSE, LINE, STAR, REGULAR_POLYGON, or RECTANGLE
is exempt from no-auto-layout. Frames with 2+ children are still flagged.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Broaden the exception from single-child-only to all-children-are-visual-
leaf-types. Frames where every child is a vector/shape type (VECTOR,
BOOLEAN_OPERATION, ELLIPSE, LINE, STAR, REGULAR_POLYGON, RECTANGLE)
are exempt from no-auto-layout — covers icons with multiple paths.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
minWidth is rarely needed as content provides a natural minimum.
Now only flags FILL containers missing maxWidth. Having maxWidth
alone is sufficient to pass the check.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Skip maxWidth check when:
- Parent already has maxWidth (parent constrains stretch)
- Root-level frame (represents the screen itself)
- Only FILL child among siblings (intent is to fill parent)
- Inside grid layout (grid controls sizing)
- Inside flex wrap (wrap layout controls sizing per row)

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Move exception logic from inline checks to dedicated functions:
- isAutoLayoutExempt: visual leaf children (icons, shapes)
- isAbsolutePositionExempt: vectors, small decorations, component internals
- isSizeConstraintExempt: maxWidth present, parent constrained, root level,
  only FILL child, grid, flex wrap, small elements

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
New exceptions added to rule-exceptions.ts:
- no-auto-layout: INSTANCE nodes (master manages layout)
- absolute-position: full-size background elements (>= 90% of parent)
- missing-size-constraint: TEXT nodes (content provides natural sizing)
- fixed-size-in-auto-layout: image fills (intentional fixed thumbnails)

Extracted isFixedSizeExempt from inline logic in fixed-size rule.
Added dedicated tests for all exception functions.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Warning

Rate limit exceeded

@let-sunny has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 4 minutes and 9 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e1f5261-72fe-49bc-99ea-298ecc36b563

📥 Commits

Reviewing files that changed from the base of the PR and between c19200f and 8861383.

📒 Files selected for processing (3)
  • src/core/rules/rule-exceptions.test.ts
  • src/core/rules/rule-exceptions.ts
  • src/core/rules/structure/index.ts
📝 Walkthrough

Walkthrough

This PR introduces centralized rule-exception utilities in a new module and refactors four structure rules to use these utilities instead of inline exemption logic, replacing hardcoded heuristics with consolidated, testable classification functions.

Changes

Cohort / File(s) Summary
Rule Exception Utilities
src/core/rules/rule-exceptions.ts, src/core/rules/rule-exceptions.test.ts
New module exporting seven classification/exemption functions (isVisualOnlyNode, isAutoLayoutExempt, isAbsolutePositionExempt, isSizeConstraintExempt, isFixedSizeExempt, isVisualLeafType, hasImageFill) with comprehensive test coverage across node types, fills, depth, layout modes, and sizing constraints.
Structure Rule Refactoring
src/core/rules/structure/index.ts
Refactored no-auto-layout, absolute-position-in-auto-layout, fixed-size-in-auto-layout, and missing-size-constraint rules to use new exemption utilities, removing approximately 66 lines of inline heuristics (graphic allowlists, size-ratio thresholds, exclusion-name checks).
Test Updates
src/core/rules/structure/no-auto-layout.test.ts, src/core/rules/structure/responsive-fields.test.ts
Added test cases for visual-only child compositions in no-auto-layout; reworked missing-size-constraint tests to verify negative scenarios (e.g., FILL with maxWidth, sibling FILL, GRID layout, wrap mode) and adjusted node hierarchy construction.
Engine Test Minor Fix
src/core/engine/rule-engine.test.ts
Changed test fixture leaf node type from "RECTANGLE" to "TEXT" in the depth-weight calculation test case.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 A rabbit hops through rule exemptions neat,
Visual leaves and image fills so sweet,
Refactored heuristics, logic once spread,
Now centered and tested, the rules well-fed! 🌱✨

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly describes the main change: adding layout rule exceptions for specific node types (icons, backgrounds, images, text). This aligns with the primary objective of centralizing exception logic and expanding exemptions across multiple rules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/icon-layout-exceptions-bYoVh

Comment @coderabbitai help to get the list of available commands and usage tips.

claude added 6 commits March 26, 2026 07:24
- Add image fill exemption to isAbsolutePositionExempt (any node with
  IMAGE fill is exempt regardless of size)
- Fix rule-engine depth weight test: use TEXT leaf instead of RECTANGLE
  to avoid triggering isAutoLayoutExempt

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
…empt

Image fills already cover background use cases — the size-based
check (>= 90% of parent) is redundant and removed.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Extract isVisualOnlyNode that covers: vector/shape types, image fills,
and frames with only visual leaf children. Use it across all exempt
functions to eliminate duplicated checks.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
These are now redundant — isVisualOnlyNode covers vectors, images,
and visual-only frames. Removed isSmallRelativeToParent,
isFullSizeRelativeToParent, and component parent check.

isAbsolutePositionExempt now takes only node (no context needed).

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Pages typically don't contain component masters, so INSTANCE nodes
should be flagged for missing auto-layout like any other container.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
…ling logic

- Move isExcludedName calls from index.ts into isAbsolutePositionExempt
  and isFixedSizeExempt — all exception logic now lives in rule-exceptions.ts
- Change missing-size-constraint sibling check: exempt when ALL siblings
  are FILL (e.g. list view), not when there's only one FILL child
- Remove unused isExcludedName import from structure/index.ts

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/rules/rule-exceptions.test.ts`:
- Around line 115-125: Add unit tests in src/core/rules/rule-exceptions.test.ts
to cover all exemption branches of isSizeConstraintExempt: create cases using
makeNode and makeContext that assert true when (1) node has maxWidth set, (2)
node is a "small" element (use whatever predicate your code uses to mark small
elements), (3) parent has maxWidth, (4) depth <= 1 (root-level frames), (5) node
is the sole FILL child among siblings, (6) parent layoutMode is "GRID", and (7)
existing TEXT node (already present); use the same pattern as the existing TEXT
test (makeNode/makeContext and expect(isSizeConstraintExempt(node,
ctx)).toBe(true)) so each exemption branch is exercised.

In `@src/core/rules/rule-exceptions.ts`:
- Around line 65-95: The sibling-based exemption in isSizeConstraintExempt must
only apply when siblings are explicitly provided: replace the loose truthy check
with an explicit Array.isArray(context.siblings) guard and compute fillSiblings
from that array (filter by layoutSizingHorizontal === "FILL"); only return true
when that array exists and fillSiblings.length <= 1 so an undefined siblings
field does not implicitly grant an exemption. Ensure you reference the existing
symbols: isSizeConstraintExempt, context.siblings, fillSiblings, and
layoutSizingHorizontal.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 454f95f5-4acf-4886-a0bc-daddd865e6ae

📥 Commits

Reviewing files that changed from the base of the PR and between 037edd1 and 89f1013.

📒 Files selected for processing (6)
  • src/core/engine/rule-engine.test.ts
  • src/core/rules/rule-exceptions.test.ts
  • src/core/rules/rule-exceptions.ts
  • src/core/rules/structure/index.ts
  • src/core/rules/structure/no-auto-layout.test.ts
  • src/core/rules/structure/responsive-fields.test.ts

Comment on lines +65 to +95
/** Nodes that don't need maxWidth even with FILL sizing */
export function isSizeConstraintExempt(node: AnalysisNode, context: RuleContext): boolean {
// Already has maxWidth
if (node.maxWidth !== undefined) return true;

// Small elements — won't stretch problematically
if (node.absoluteBoundingBox && node.absoluteBoundingBox.width <= 200) return true;

// Parent already has maxWidth — parent constrains the stretch
if (context.parent?.maxWidth !== undefined) return true;

// Root-level frames — they represent the screen itself
if (context.depth <= 1) return true;

// Only FILL child among siblings — intent is to fill the parent entirely
if (context.siblings) {
const fillSiblings = context.siblings.filter((s) => s.layoutSizingHorizontal === "FILL");
if (fillSiblings.length <= 1) return true;
}

// Inside grid layout — grid controls sizing
if (context.parent?.layoutMode === "GRID") return true;

// Inside flex wrap — wrap layout controls sizing per row
if (context.parent?.layoutWrap === "WRAP") return true;

// Text nodes — content length provides natural sizing
if (node.type === "TEXT") return true;

return false;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential false exemption when siblings is undefined.

When context.siblings is undefined (which can happen per the RuleContext interface), the fillSiblings filter block is skipped entirely. However, this means a node that should be flagged (e.g., multiple FILL siblings exist but siblings wasn't populated) could incorrectly pass through to later checks and potentially get exempted by other conditions.

Consider whether this is the intended behavior. If siblings being undefined should not grant an exemption, you may want to only apply this exemption when siblings are explicitly provided and the condition is met.

Suggested clarification
   // Only FILL child among siblings — intent is to fill the parent entirely
-  if (context.siblings) {
+  if (context.siblings && context.siblings.length > 0) {
     const fillSiblings = context.siblings.filter((s) => s.layoutSizingHorizontal === "FILL");
     if (fillSiblings.length <= 1) return true;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/rule-exceptions.ts` around lines 65 - 95, The sibling-based
exemption in isSizeConstraintExempt must only apply when siblings are explicitly
provided: replace the loose truthy check with an explicit
Array.isArray(context.siblings) guard and compute fillSiblings from that array
(filter by layoutSizingHorizontal === "FILL"); only return true when that array
exists and fillSiblings.length <= 1 so an undefined siblings field does not
implicitly grant an exemption. Ensure you reference the existing symbols:
isSizeConstraintExempt, context.siblings, fillSiblings, and
layoutSizingHorizontal.

claude and others added 6 commits March 26, 2026 07:41
Add tests for all exemption conditions: maxWidth present, small
elements, parent maxWidth, root-level, all-FILL siblings, GRID,
flex wrap, mixed sizing (negative case). Addresses CodeRabbit review.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Define PR lifecycle: draft first, subscribe for reviews, CodeRabbit
rate limit handling, squash merge with branch deletion, user
confirmation required for merge.

https://claude.ai/code/session_01BXadwG43WaXRGwckkERMbN
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CLAUDE.md`:
- Line 238: Step 4 is a long, compound sentence — split it into 2–4 concise
sub-steps: (a) "After each push, watch for CodeRabbit's first comment." (b) "If
that comment contains a rate limit message, wait the specified duration." (c)
"After waiting, re-trigger the review by pushing an empty commit." Keep the
exact retry instruction text (git commit --allow-empty -m "chore: re-trigger
review") intact so readers know the exact command to run.

In `@src/core/rules/rule-exceptions.test.ts`:
- Around line 176-184: The test currently hits the sibling-FILL exemption before
the TEXT branch in isSizeConstraintExempt; update the test so the sibling
exemption does not trigger (e.g., make the other sibling's
layoutSizingHorizontal a non-FILL value or remove the sibling) so the function
evaluates node.type === "TEXT"; locate the test in rule-exceptions.test.ts (the
it("exempts TEXT nodes") block) and modify the siblings array to ensure only the
node under test is FILL while the sibling is FIXED/UNSET so the TEXT-specific
branch is actually exercised.

In `@src/core/rules/rule-exceptions.ts`:
- Around line 18-23: The hasImageFill function currently assumes each element in
node.fills is an indexable object; update it to guard each entry before reading
its "type": inside hasImageFill (and when iterating node.fills), skip entries
that are null/undefined or not objects (typeof !== "object" or Array.isArray)
and only then access the "type" property (also verify it's a string) and compare
to "IMAGE"; this prevents runtime throws when fills contains unknown values
while keeping the same overall logic.
- Around line 45-48: The exemption logic in isAutoLayoutExempt is incorrect:
replace the broad isVisualOnlyNode(node) check with a rule that returns true for
INSTANCE nodes and for image-backed frames that are truly visual-only (i.e.,
image/icon-like with no meaningful content children). Update isAutoLayoutExempt
to: return true if node.type === 'INSTANCE' OR (isVisualOnlyNode(node) AND the
node has no non-visual/content children), using the AnalysisNode children
metadata to detect real content children; keep all other nodes falling through
to the overlap/nested-container checks.

In `@src/core/rules/structure/index.ts`:
- Around line 139-145: The exemption check for full-bleed backgrounds is called
only with the child node, so it cannot compare the child's size to its
auto-layout parent; update the call in absolutePositionInAutoLayoutCheck to pass
the parent (context.parent) or the parent's bounds instead of only node, and
update the isAbsolutePositionExempt function signature/logic to accept the
parent (or parentBounds) and use that to compute the coverage percentage before
returning true; ensure callers of isAbsolutePositionExempt are adjusted
accordingly so the full-bleed background exemption works as intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41624e3e-a981-4770-a214-851072c9cbff

📥 Commits

Reviewing files that changed from the base of the PR and between 89f1013 and f8c15d9.

📒 Files selected for processing (5)
  • CLAUDE.md
  • src/core/rules/rule-exceptions.test.ts
  • src/core/rules/rule-exceptions.ts
  • src/core/rules/structure/index.ts
  • src/core/rules/structure/responsive-fields.test.ts

CLAUDE.md Outdated
1. Always create PRs as **draft** first — wait for user approval before marking ready
2. When changes are needed, convert back to **draft** — mark ready again when done
3. After creating a PR, **subscribe** with `subscribe_pr_activity` to monitor reviews and CI in real-time
4. After each push, watch for CodeRabbit's first comment — if it contains a rate limit message, wait the specified duration then push an empty commit (`git commit --allow-empty -m "chore: re-trigger review"`) to re-trigger
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider breaking step 4 into sub-steps for better readability.

Step 4 packs multiple conditional actions (watch → detect → wait → retry) into a single long sentence. Breaking it into sub-steps or shorter sentences would make it easier to parse.

♻️ Proposed refactor for improved readability
-4. After each push, watch for CodeRabbit's first comment — if it contains a rate limit message, wait the specified duration then push an empty commit (`git commit --allow-empty -m "chore: re-trigger review"`) to re-trigger
+4. After each push, watch for CodeRabbit's first comment:
+   - If it contains a rate limit message, wait the specified duration
+   - Then push an empty commit to re-trigger: `git commit --allow-empty -m "chore: re-trigger review"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
4. After each push, watch for CodeRabbit's first comment — if it contains a rate limit message, wait the specified duration then push an empty commit (`git commit --allow-empty -m "chore: re-trigger review"`) to re-trigger
4. After each push, watch for CodeRabbit's first comment:
- If it contains a rate limit message, wait the specified duration
- Then push an empty commit to re-trigger: `git commit --allow-empty -m "chore: re-trigger review"`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CLAUDE.md` at line 238, Step 4 is a long, compound sentence — split it into
2–4 concise sub-steps: (a) "After each push, watch for CodeRabbit's first
comment." (b) "If that comment contains a rate limit message, wait the specified
duration." (c) "After waiting, re-trigger the review by pushing an empty
commit." Keep the exact retry instruction text (git commit --allow-empty -m
"chore: re-trigger review") intact so readers know the exact command to run.

Comment on lines +45 to +48
export function isAutoLayoutExempt(node: AnalysisNode): boolean {
if (isVisualOnlyNode(node)) return true;

return false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

no-auto-layout exemption is both too broad and too narrow.

Using isVisualOnlyNode() here exempts any image-filled frame, including containers that still have real content children, while plain INSTANCE nodes can still fall through into the overlap/nested-container checks. That reverses the PR goal: icon-like visual-child frames and all instances should skip, not every image-backed container.

🎯 Proposed fix
 export function isAutoLayoutExempt(node: AnalysisNode): boolean {
-  if (isVisualOnlyNode(node)) return true;
-
-  return false;
+  if (node.type === "INSTANCE") return true;
+  if (
+    node.type === "FRAME" &&
+    node.children &&
+    node.children.length > 0 &&
+    node.children.every((child) => isVisualLeafType(child.type))
+  ) {
+    return true;
+  }
+  return false;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/rules/rule-exceptions.ts` around lines 45 - 48, The exemption logic
in isAutoLayoutExempt is incorrect: replace the broad isVisualOnlyNode(node)
check with a rule that returns true for INSTANCE nodes and for image-backed
frames that are truly visual-only (i.e., image/icon-like with no meaningful
content children). Update isAutoLayoutExempt to: return true if node.type ===
'INSTANCE' OR (isVisualOnlyNode(node) AND the node has no non-visual/content
children), using the AnalysisNode children metadata to detect real content
children; keep all other nodes falling through to the overlap/nested-container
checks.

…s, revert CLAUDE.md

- isAbsolutePositionExempt: restore small-element-relative-to-parent check
  (badges/decorations < 25% of parent) that was removed in 2a5c745
- fixedSizeInAutoLayout: remove duplicate isFixedSizeExempt call
- noAutoLayout: simplify redundant type guard (isContainerNode covers FRAME)
- isSizeConstraintExempt: clarify comment for single/all-FILL siblings
- Revert CLAUDE.md PR workflow changes (belongs in separate PR)
- Add tests for small-decoration exemption
…ePositionExempt

Size-based heuristics guess intent; name patterns reflect explicit designer intent.
If a small element needs absolute positioning exemption, it should be named properly
(badge, indicator, etc.) — which is already covered by EXCLUDED_NAME_PATTERN.
@let-sunny let-sunny merged commit 145f720 into main Mar 26, 2026
3 checks passed
@let-sunny let-sunny deleted the claude/icon-layout-exceptions-bYoVh branch March 26, 2026 08:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants