From 8dae8983040771346d1d9498a37a8d50fdb8b90c Mon Sep 17 00:00:00 2001 From: John Dvorak Date: Thu, 21 May 2026 14:11:47 -0700 Subject: [PATCH] feat: annotate inside/contains overflow metrics with clipping awareness - insidePredicate: add hasClippedOverflow=1 when reference element clips its content (contain:paint or overflow:hidden) and the subject overflows beyond the reference bounds - containsPredicate: same, checking the subject (container) clips - Reads world.clipping table to determine if the container clips - Safe when clipping data is absent (unit test fixtures) --- packages/imhotep-solver/src/predicates.ts | 28 +++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/imhotep-solver/src/predicates.ts b/packages/imhotep-solver/src/predicates.ts index f7486e2..69aba27 100644 --- a/packages/imhotep-solver/src/predicates.ts +++ b/packages/imhotep-solver/src/predicates.ts @@ -459,7 +459,7 @@ export const insidePredicate: PredicateEvaluator = { sRect.top >= rRect.top - effectiveTolerance && sRect.right <= rRect.right + effectiveTolerance && sRect.bottom <= rRect.bottom + effectiveTolerance; - const metrics = { + const metrics: Record = { overflowLeft: Math.max(0, rRect.left - sRect.left), overflowTop: Math.max(0, rRect.top - sRect.top), overflowRight: Math.max(0, sRect.right - rRect.right), @@ -476,6 +476,16 @@ export const insidePredicate: PredicateEvaluator = { refRight: rRect.right, refBottom: rRect.bottom, }; + const hasOverflow = metrics.overflowLeft > 0 || metrics.overflowTop > 0 + || metrics.overflowRight > 0 || metrics.overflowBottom > 0; + if (hasOverflow && world.clipping?.subjectId) { + for (let i = 0; i < world.clipping.subjectId.length; i++) { + if (world.clipping.subjectId[i] === referenceId) { + metrics.hasClippedOverflow = 1; + break; + } + } + } const diagnostics = pass ? undefined : makePredicateDiagnostic('inside', metrics, [subjectId, referenceId]); return makePredicateResult(pass ? 'true' : 'false', metrics, [subjectId, referenceId], diagnostics); }, @@ -499,7 +509,7 @@ export const containsPredicate: PredicateEvaluator = { rRect.top >= sRect.top - tolerance && rRect.right <= sRect.right + tolerance && rRect.bottom <= sRect.bottom + tolerance; - const metrics = { + const metrics: Record = { tolerance, subjectLeft: sRect.left, subjectTop: sRect.top, @@ -510,6 +520,20 @@ export const containsPredicate: PredicateEvaluator = { refRight: rRect.right, refBottom: rRect.bottom, }; + const overflowLeft = Math.max(0, sRect.left - rRect.left); + const overflowTop = Math.max(0, sRect.top - rRect.top); + const overflowRight = Math.max(0, rRect.right - sRect.right); + const overflowBottom = Math.max(0, rRect.bottom - sRect.bottom); + if (overflowLeft > 0 || overflowTop > 0 || overflowRight > 0 || overflowBottom > 0) { + if (world.clipping?.subjectId) { + for (let i = 0; i < world.clipping.subjectId.length; i++) { + if (world.clipping.subjectId[i] === subjectId) { + metrics.hasClippedOverflow = 1; + break; + } + } + } + } const diagnostics = pass ? undefined : makePredicateDiagnostic('contains', metrics, [subjectId, referenceId]); return makePredicateResult(pass ? 'true' : 'false', metrics, [subjectId, referenceId], diagnostics); },