fix(core): add iteration bound to grid auto-placement to prevent infinite loop#1678
Conversation
…nite loop The grid auto-placement algorithm used a while(true) loop with no termination bound. When a child's rowSpan exceeded available space or all slots were occupied, the loop ran forever, hanging the application. Add a maximum row limit (numCols * 100, minimum 1000) so the loop terminates. Children that cannot fit are silently skipped rather than causing an infinite loop. Closes Karanjot786#1661
|
Hi @atul-upadhyay-7 👋 ⭐ Star this repo before your PR merges. Why? GSSoC 2026 contributors who star get priority review and points credit. After you star, push any commit (or re-run this check). The Thanks for your contribution to TermUI. |
📝 WalkthroughWalkthroughFixes an infinite loop in CSS Grid Auto-Placement Termination Fix
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/core/src/layout/LayoutEngine.ts (1)
222-269:⚠️ Potential issue | 🟠 Major | ⚡ Quick winDo not mutate the global auto-placement cursor during probe scans.
At Line 223, the scan loop advances
currentAutoRow/currentAutoColdirectly. If a child cannot be placed before the cap, the cursor ends atmaxAutoRow, and later auto-children are skipped without attempts. Theplacedflag set at Line 259 is never used to prevent that.Suggested fix
- let placed = false; - while (currentAutoRow < maxAutoRow) { + let probeRow = currentAutoRow; + let probeCol = currentAutoCol; + let placed = false; + while (probeRow < maxAutoRow) { let available = true; - for (let r = currentAutoRow; r < currentAutoRow + rowInfo.span; r++) { - for (let c = currentAutoCol; c < currentAutoCol + clampedColSpan; c++) { + for (let r = probeRow; r < probeRow + rowInfo.span; r++) { + for (let c = probeCol; c < probeCol + clampedColSpan; c++) { if (c >= numCols) { available = false; break; @@ if (available) { placements.push({ child, - row: currentAutoRow, - col: currentAutoCol, + row: probeRow, + col: probeCol, rowSpan: rowInfo.span, colSpan: clampedColSpan }); - for (let r = currentAutoRow; r < currentAutoRow + rowInfo.span; r++) { - for (let c = currentAutoCol; c < currentAutoCol + clampedColSpan; c++) { + for (let r = probeRow; r < probeRow + rowInfo.span; r++) { + for (let c = probeCol; c < probeCol + clampedColSpan; c++) { setOccupied(r, c, true); } } - currentAutoCol += clampedColSpan; + currentAutoRow = probeRow; + currentAutoCol = probeCol + clampedColSpan; if (currentAutoCol >= numCols) { currentAutoCol = 0; currentAutoRow++; } placed = true; break; } else { - currentAutoCol++; - if (currentAutoCol >= numCols) { - currentAutoCol = 0; - currentAutoRow++; + probeCol++; + if (probeCol >= numCols) { + probeCol = 0; + probeRow++; } } } + if (!placed) continue;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/core/src/layout/LayoutEngine.ts` around lines 222 - 269, The auto-placement cursor position (currentAutoRow and currentAutoCol) is being mutated during the probe scanning loop within the while loop condition that checks currentAutoRow against maxAutoRow. Instead of modifying the global cursor during the probe phase, use temporary variables (such as probeRow and probeCol) to track the candidate position while scanning for available space in the nested for loops. Only update the actual currentAutoRow and currentAutoCol values when a placement is successfully found, and use the placed flag to break out of the while loop after a successful placement to ensure the cursor position is preserved correctly for subsequent auto-placed children.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/layout/LayoutEngine.test.ts`:
- Around line 251-262: The test titled 'terminates when rowSpan exceeds
available space' does not actually configure a rowSpan case - it only sets
height properties on the child nodes without configuring any grid row spanning
attributes. To properly test the rowSpan edge case, modify one or more of the
child nodes created in the children array to include gridRowStart and gridRowEnd
properties that create a span exceeding the available vertical space of 10
pixels. For example, set gridRowStart to 1 and gridRowEnd to a value that
creates a rowSpan larger than the available container height to properly
exercise the intended edge case behavior.
---
Outside diff comments:
In `@packages/core/src/layout/LayoutEngine.ts`:
- Around line 222-269: The auto-placement cursor position (currentAutoRow and
currentAutoCol) is being mutated during the probe scanning loop within the while
loop condition that checks currentAutoRow against maxAutoRow. Instead of
modifying the global cursor during the probe phase, use temporary variables
(such as probeRow and probeCol) to track the candidate position while scanning
for available space in the nested for loops. Only update the actual
currentAutoRow and currentAutoCol values when a placement is successfully found,
and use the placed flag to break out of the while loop after a successful
placement to ensure the cursor position is preserved correctly for subsequent
auto-placed children.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: df25d743-4dee-44b8-a26d-b8dc854ab914
📒 Files selected for processing (2)
packages/core/src/layout/LayoutEngine.test.tspackages/core/src/layout/LayoutEngine.ts
| it('terminates when rowSpan exceeds available space', () => { | ||
| const children = []; | ||
| for (let i = 0; i < 5; i++) { | ||
| children.push(makeNode(`c${i}`, { height: 10 })); | ||
| } | ||
| const root = makeNode('root', { | ||
| display: 'grid', | ||
| gridTemplateColumns: '1fr 1fr', | ||
| }, children); | ||
|
|
||
| expect(() => computeLayout(root, 20, 10)).not.toThrow(); | ||
| }); |
There was a problem hiding this comment.
The “rowSpan exceeds available space” test does not create a rowSpan case.
Line 251 says rowSpan coverage, but Line 254 only sets height; no gridRowStart/gridRowEnd span is configured, so this does not exercise the intended edge case.
Suggested fix
- it('terminates when rowSpan exceeds available space', () => {
- const children = [];
- for (let i = 0; i < 5; i++) {
- children.push(makeNode(`c${i}`, { height: 10 }));
- }
+ it('terminates when rowSpan exceeds available space', () => {
+ const children = [
+ makeNode('oversized', { gridRowStart: 1, gridRowEnd: 'span 5000', height: 1 }),
+ makeNode('next', { height: 1 }),
+ ];
const root = makeNode('root', {
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}, children);
expect(() => computeLayout(root, 20, 10)).not.toThrow();
+ // Ensure a later placeable child is still processed.
+ expect(root.children[1].computed.width).toBeGreaterThanOrEqual(0);
});📝 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.
| it('terminates when rowSpan exceeds available space', () => { | |
| const children = []; | |
| for (let i = 0; i < 5; i++) { | |
| children.push(makeNode(`c${i}`, { height: 10 })); | |
| } | |
| const root = makeNode('root', { | |
| display: 'grid', | |
| gridTemplateColumns: '1fr 1fr', | |
| }, children); | |
| expect(() => computeLayout(root, 20, 10)).not.toThrow(); | |
| }); | |
| it('terminates when rowSpan exceeds available space', () => { | |
| const children = [ | |
| makeNode('oversized', { gridRowStart: 1, gridRowEnd: 'span 5000', height: 1 }), | |
| makeNode('next', { height: 1 }), | |
| ]; | |
| const root = makeNode('root', { | |
| display: 'grid', | |
| gridTemplateColumns: '1fr 1fr', | |
| }, children); | |
| expect(() => computeLayout(root, 20, 10)).not.toThrow(); | |
| // Ensure a later placeable child is still processed. | |
| expect(root.children[1].computed.width).toBeGreaterThanOrEqual(0); | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/layout/LayoutEngine.test.ts` around lines 251 - 262, The
test titled 'terminates when rowSpan exceeds available space' does not actually
configure a rowSpan case - it only sets height properties on the child nodes
without configuring any grid row spanning attributes. To properly test the
rowSpan edge case, modify one or more of the child nodes created in the children
array to include gridRowStart and gridRowEnd properties that create a span
exceeding the available vertical space of 10 pixels. For example, set
gridRowStart to 1 and gridRowEnd to a value that creates a rowSpan larger than
the available container height to properly exercise the intended edge case
behavior.
Summary
Fixes an infinite loop in the CSS Grid auto-placement algorithm that hung the application when a child's
rowSpanexceeded available space or all grid slots were occupied.Closes #1661
Root Cause
The auto-placement loop used
while (true)with no termination bound. When searching for a slot, it kept incrementingcurrentAutoRowindefinitely if no valid position existed — for example when a child'srowSpanwas larger than the grid, or when all slots were already filled by explicitly-placed children.Fix
Added a maximum row limit to the auto-placement loop:
numCols * 100) to handle wide gridsTests added
Verification
Summary by CodeRabbit
Bug Fixes
Tests