Skip to content

Commit 8c1c3e0

Browse files
committed
fix(autolayout): harden note overlap resolution against resize and non-finite positions
1 parent 67019b0 commit 8c1c3e0

2 files changed

Lines changed: 32 additions & 4 deletions

File tree

apps/sim/lib/workflows/autolayout/utils.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,31 @@ describe('resolveNoteOverlaps', () => {
114114
expect(blocks.b.position).toEqual({ x: 500, y: 150 })
115115
})
116116

117+
it('never produces non-finite coordinates when a block has a NaN position', () => {
118+
const blocks: Record<string, BlockState> = {
119+
bad: createBlock('bad', 'agent', { x: Number.NaN, y: Number.NaN }),
120+
a: createBlock('a', 'agent', { x: 150, y: 150 }),
121+
note: createBlock(
122+
'note',
123+
'note',
124+
{ x: 150, y: 150 },
125+
{
126+
height: 120,
127+
layout: { measuredHeight: 120 },
128+
}
129+
),
130+
}
131+
132+
resolveNoteOverlaps(blocks, DEFAULT_VERTICAL_SPACING)
133+
134+
// The corrupted block is ignored; the note still relocates off block "a"
135+
// using only finite coordinates.
136+
expect(Number.isFinite(blocks.note.position.x)).toBe(true)
137+
expect(Number.isFinite(blocks.note.position.y)).toBe(true)
138+
expect(blocks.note.position.x).toBe(150)
139+
expect(blocks.note.position.y).toBeGreaterThan(150)
140+
})
141+
117142
describe('targeted mode (previousBlocks)', () => {
118143
it('relocates a note when a block was moved onto it', () => {
119144
const previousBlocks: Record<string, BlockState> = {

apps/sim/lib/workflows/autolayout/utils.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,6 @@ export function hasFinitePosition(block: BlockState): boolean {
357357
function noteOverlappedBlockBefore(
358358
previousBlocks: Record<string, BlockState>,
359359
noteId: string,
360-
noteDimensions: { width: number; height: number },
361360
blockId: string
362361
): boolean {
363362
const previousNote = previousBlocks[noteId]
@@ -368,7 +367,9 @@ function noteOverlappedBlockBefore(
368367
// so it could not have overlapped anything before this pass.
369368
if (!hasFinitePosition(previousNote) || !hasFinitePosition(previousBlock)) return false
370369

371-
const noteBox = createBoundingBox(previousNote.position, noteDimensions)
370+
// Derive dimensions from the prior blocks so a resize between passes does not
371+
// pair new dimensions with the old position.
372+
const noteBox = createBoundingBox(previousNote.position, getNoteDimensions(previousNote))
372373
const blockBox = createBoundingBox(previousBlock.position, getBlockMetrics(previousBlock))
373374
return boxesOverlap(blockBox, noteBox)
374375
}
@@ -415,7 +416,9 @@ export function resolveNoteOverlaps(
415416

416417
for (const id of groupIds) {
417418
const block = blocks[id]
418-
if (!block) continue
419+
// Skip non-finite positions so corrupted coordinates never propagate into
420+
// minX / maxBottom (and therefore into relocated note positions).
421+
if (!block || !hasFinitePosition(block)) continue
419422

420423
if (block.type === NOTE_BLOCK_TYPE) {
421424
noteIds.push(id)
@@ -450,7 +453,7 @@ export function resolveNoteOverlaps(
450453
const needsRelocation = obstacles.some(({ id: blockId, box }) => {
451454
if (!boxesOverlap(box, noteBox)) return false
452455
if (previousBlocks) {
453-
return !noteOverlappedBlockBefore(previousBlocks, id, dimensions, blockId)
456+
return !noteOverlappedBlockBefore(previousBlocks, id, blockId)
454457
}
455458
return true
456459
})

0 commit comments

Comments
 (0)