Skip to content

Commit 221c591

Browse files
committed
docs(search): describe maxEnd as a bound, not the exact maximum
Review pointed out the comment claimed `maxEnd` is "the largest range.end currently kept in the bucket", which stops being true the moment a replacement swaps in a range that ends earlier - it is only ever widened. Only the upper bound is load-bearing, so say that. A bound left too high costs a scan that would have been skipped, never a wrong answer, and the staleness is capped at one token length because every range spans a matched token rather than the field. Also records why the exact maximum is deliberately not recomputed: on the realistic overlap shape at 10k matches, recomputing measures 45ms against 23ms as written, and 1010ms for the scan this replaced.
1 parent 12c677e commit 221c591

1 file changed

Lines changed: 17 additions & 6 deletions

File tree

apps/sim/lib/workflows/search-replace/resources/resolvers.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ function shouldPreferOverlappingMatch(
146146
return false
147147
}
148148

149-
/** Kept indices for one overlap scope, plus the highest `range.end` among them. */
149+
/** Kept indices for one overlap scope, plus an upper bound on their `range.end`. */
150150
interface RangeMatchScopeBucket {
151151
indices: number[]
152152
maxEnd: number
@@ -168,11 +168,22 @@ function widenScopeBucket(bucket: RangeMatchScopeBucket, end: number): void {
168168
* insertion order and the scan stops at the first overlap, so this picks the
169169
* same candidate the linear scan did.
170170
*
171-
* `maxEnd` is the largest `range.end` currently kept in the bucket. A match
172-
* starting at or after it cannot overlap anything in that bucket, so the scan
173-
* is skipped. That keeps a single long field full of disjoint hits linear
174-
* instead of quadratic within its own bucket, to the extent its matches arrive
175-
* in ascending offset order; out-of-order producers just fall back to scanning.
171+
* `maxEnd` is a monotonic high-water mark, not the exact current maximum: a
172+
* replacement can swap in a range that ends earlier without lowering it. Only
173+
* the upper bound is load-bearing. A match starting at or after it cannot
174+
* overlap anything in the bucket, so the scan is skipped; a bound left too high
175+
* only costs a scan that would have been skipped, never a wrong answer.
176+
*
177+
* Recomputing the exact maximum on every shrinking replacement is a net loss -
178+
* it walks the bucket, which is the cost this is here to avoid, and staleness
179+
* is capped at one token length because every range spans a matched token
180+
* (`query.length`, or a reference's `rawValue.length`) rather than the field.
181+
* Measured on the realistic overlap shape at 10k matches: 23ms as written,
182+
* 45ms with the recompute, against 1010ms for the scan this replaced.
183+
*
184+
* The bound keeps a field full of disjoint hits linear instead of quadratic
185+
* within its own bucket, to the extent its matches arrive in ascending offset
186+
* order; out-of-order producers just fall back to scanning.
176187
*
177188
* It must be refreshed on the replacement path too, not only on append:
178189
* `shouldPreferOverlappingMatch` prefers the SHORTER range, and a shorter range

0 commit comments

Comments
 (0)