Skip to content

Fix skip-list edge cases with zero totals and negative values - #555

Merged
mixonic merged 3 commits into
html-next:mainfrom
lukemelia:fix-skip-list-edge-cases
Apr 7, 2026
Merged

Fix skip-list edge cases with zero totals and negative values#555
mixonic merged 3 commits into
html-next:mainfrom
lukemelia:fix-skip-list-edge-cases

Conversation

@lukemelia

Copy link
Copy Markdown
Contributor

Summary

This PR fixes three edge cases in vertical-collection/src/-private/data-view/skip-list.js that can cause crashes or corrupted layout state when item heights are zero or negative.

Fix 1: Early return in find when total <= 0

When a SkipList is constructed with items that all have zero height (e.g. items not yet measured), total will be 0. The previous code would proceed to compute Math.min(total - 1, targetValue)Math.min(-1, targetValue), resulting in a negative targetValue that immediately triggers the assertion targetValue must be greater than or equal to 0 and throws.

The fix returns { index: 0, totalBefore: 0, totalAfter: 0 } early when total <= 0, matching the same early-return pattern already used for length === 0.

Fix 2: Clamp targetValue to >= 0

Even after the total <= 0 guard, if targetValue is negative (e.g. a negative scrollTop during rubber-band scroll on iOS), Math.min(total - 1, targetValue) may still yield a negative value. Adding Math.max(0, targetValue) after the Math.min ensures the value stays in bounds before the assertion.

Fix 3: Clamp value in set to >= 0

When measuring item heights, a layout engine can occasionally return a negative or -0 value. Passing such a value to set triggers the assertion value must non-negative and throws, leaving the list in a partially-updated state. Clamping with Math.max(0, value) silently coerces bad measurements to 0 before the assertion, preventing corruption.


Related issue: #208

lukemelia and others added 2 commits April 6, 2026 18:11
- Return early from `find` when `total <= 0` to avoid crashes on empty lists
- Clamp `targetValue` to >= 0 with `Math.max(0, targetValue)` after the `Math.min` call
- Clamp `value` in `set` to >= 0 with `Math.max(0, value)` to prevent negative heights corrupting layout
Tests cover:
- find() with zero total (all items have height 0)
- find() with empty list (length 0)
- find() with negative targetValue (clamped to 0)
- find() with targetValue exceeding total (clamped to last item)
- set() with negative value (clamped to 0)
- set() with valid value (baseline behavior)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@mixonic mixonic left a comment

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.

Nice.

@mixonic

mixonic commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

The failures of ember-beta, ember-canary and Floating Dependencies are unrelated to this change. Those are due to changes in Ember 7 IIRC, and need their own attention.

@mixonic
mixonic merged commit 7d1fa15 into html-next:main Apr 7, 2026
8 of 11 checks passed
@github-actions github-actions Bot mentioned this pull request Apr 7, 2026
@mixonic mixonic added the bug label Apr 7, 2026
@mixonic

mixonic commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

In v5.0.4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants