Skip to content

v4.191.0

@jessegall jessegall tagged this 25 Jul 06:52
A new frontend recurrence DETECTOR would have been redundant — DuplicateElementDetector
already buckets by structural hash across every component and keeps the largest. It IS
the recurrence rule. The real gap was that the CONTRACT was backend-typed
(Ast\NodeMatch, Ast\Codebase), so FrontendFixture::recurrenceSpans() returned [] with a
"backend-only today" note and the Vue fixture was never held to the cross-file proof its
backend siblings carry.

RecurrenceDetector now speaks base types (Located, the root Codebase), and each engine
narrows at ONE seam: Backend\RecurringPattern does the instanceof once and asks its
subclasses for a `fingerprint` instead. The three backend detectors changed a signature
and nothing else. RecurrenceSpanResolver reads a finding's file through Located, so both
fixtures share the one resolver rather than each growing a copy.

NodeMatch implements Located (it already had location() and scope(); it gains file()),
which is what let the shared resolver stop caring which engine produced a finding.

DuplicateElementDetector is enrolled, and the Vue fixture earns the proof: it had six
duplicate groups and every one sat inside a single component, so the structural hash was
never shown to bucket ACROSS files. Two promo banners now do exactly that.

Smoke-tested on a real Vue tree (prep-labels, 24 duplicate findings, cross-file groups).
Assets 2
Loading