Perf: per-rule WeakSet for processed-node skip bypass (#150 Tier 2)#159
Merged
Conversation
Adds a WeakSet<HTMLElement> in each selector-hide-rule's closure that
caches "already concluded skip" for elements based on their own state
(PLACEHOLDER_CLASS, REVEALED_ATTR === id, HIDDEN_ATTR === id, and the
hide branches). Subsequent scans short-circuit at the top of the loop,
saving the 5 marker reads on infinite-scroll feeds and SPA route
sweeps where the dispatcher's QSA re-finds every already-processed
candidate.
Markers stay in the DOM for cross-rule coordination and for reveal
click handlers; the WeakSet is a perf bypass only. Ancestor-relative
skips (closest('.placeholder') / closest('[REVEALED_ATTR=id]'))
intentionally do NOT add to the set — the element could later move
out from under the matched ancestor, and a memoized skip would
silently miss the new eligibility.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Strongest property the WeakSet bypass must satisfy: with the DOM held fixed between calls, apply once produces the same DOM as apply N (for N in [2, 6]). Fuzzes random tree shape, random target mask, and random pre-existing marker seed per node (none / PLACEHOLDER_CLASS / HIDDEN_ATTR for this rule / HIDDEN_ATTR for another rule / REVEALED_ATTR for this rule / REVEALED_ATTR for another rule). Two properties: one for placeholder mode and one for removeEntirely mode. A divergence — innerHTML drifting between the first apply and the N-th — would surface any case where the WeakSet's membership decision disagrees with the marker-based skip ladder. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements item 14 from issue #150.
Summary
Each selector-hide-rule's
scanloop currently does 5 marker reads per surviving candidate (PLACEHOLDER_CLASS classList check, twoclosest()walks, twogetAttributecalls) before deciding to skip or hide. On infinite-scroll feeds and SPA route sweeps the same candidates keep surfacing batch after batch — the dispatcher's QSA re-finds every element the rule already processed, and the marker reads re-run every time.This PR adds a per-rule
WeakSet<HTMLElement>in the factory closure. When the loop conclusively decides to skip (or after it hides), the element joins the set. The next scan short-circuits at the top of the loop.Why a cache, not a code change to the markers
Markers stay in the DOM for cross-rule coordination (other rules read
HIDDEN_ATTR/REVEALED_ATTR) and for the reveal click handlers (the placeholder click flow looks upREVEALED_ATTRon the restored element). The WeakSet is purely a hot-loop bypass for this rule's own re-scans — same conclusions, fewer DOM reads.Safety boundary
Membership is added only when the skip reason is the element's own state:
element.classList.contains(PLACEHOLDER_CLASS)element.closest('.placeholder')element.getAttribute(REVEALED_ATTR) === idelement.closest('[REVEALED_ATTR=id]')element.getAttribute(HIDDEN_ATTR) === idIf we memoized the ancestor-relative checks, a candidate that moves out of a revealed wrapper would be silently missed on subsequent scans. The tests pin this down explicitly.
Test plan
bun run test— 1261 / 1261 passbun run typecheckbun run check(biome + eslint)bun run knipclosest('.placeholder')andclosest('[REVEALED_ATTR=id]')(move-out scenarios), and a spy-based assertion that a placeholder candidate'sgetAttributecall count doesn't grow between scans.applyequals the DOM after N applies (N up to 6). One property for placeholder mode, one for removeEntirely. A regression in WeakSet membership — added when it shouldn't be, or not added when it should be — would surface as innerHTML drift between the first apply and the N-th.🤖 Generated with Claude Code