The second consumer drops its copy of the highlighter

people-groups-grid is the file whose duplicate earned useTextHighlight its place in
library/ under RULE 2, and studio agreed to treat it as unowned and asked for it to
be cut. Its hand-rolled range walk is replaced by highlightRanges(value,
match.indices), and its local escapeHtml goes with it: once the loop moved, that
function had ZERO callers left.

★ ONE CORRECTION TO THE STATED REASON. studio approved this with "removing the
null-throw is reason enough". Measured before accepting it: that escaper has exactly
three callers, all inside `if (value && typeof value === 'string')`, so the throw
was NOT REACHABLE here. The library escaper's null guard is still an improvement in
principle, but it fixes nothing that could fire in this file, and I would rather
carry the smaller true reason than the larger convenient one.

The reason that does hold is the original one: the same construction existed twice,
character for character apart from variable names. One fact, one copy.

The gate flips with it — check:text-highlight now asserts this file routes through
the helper, that its escapeHtml is gone, and that the hand-rolled range walk is
really removed rather than merely shadowed by the new import.
This commit is contained in:
Doxa Maps 2026-09-26 21:55:16 +00:00
parent 29bbab3967
commit 02b2e9d59a
2 changed files with 11 additions and 21 deletions

View file

@ -42,6 +42,7 @@
import { ref, computed, watch, onMounted, getCurrentInstance } from 'vue'
// SHARED search primitives (one source of truth for BOTH cap-2 grid + cap-1 dashboard).
import Fuse from '@map/components/search/fuzzy.js'
import { highlightRanges } from '@map/composables/useTextHighlight.js'
import SearchBar from '@map/components/search/SearchBar.vue'
import FilterDropdown from '@map/components/search/FilterDropdown.vue'
import { resolveAssetsBase } from '@map/utils/embedBase.js'
@ -474,10 +475,6 @@ const visibleFilterCount = computed(() => Object.keys(activeFilters.value).lengt
// ref is always empty there and only the prop is real.
const isNarrowed = computed(() => activeFilterCount.value > 0 || !!(effSearchTerm.value || '').trim())
function escapeHtml(s) {
return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
.replace(/"/g, '&quot;').replace(/'/g, '&#39;')
}
function debounce(fn, ms = 500) {
let timeout
@ -731,18 +728,9 @@ function filterUUPGs() {
value = newItem[String(matchKey)]
}
if (value && typeof value === 'string') {
let currentIndex = 0
let highlightedValue = ''
match.indices.forEach((idx, i) => {
const start = idx[0]
const end = idx[1]
const isLast = match.indices.length - 1 === i
highlightedValue
+= escapeHtml(value.slice(currentIndex, start))
+ `<span class="search-highlight">${escapeHtml(value.slice(start, end + 1))}</span>`
+ (isLast ? escapeHtml(value.slice(end + 1)) : '')
currentIndex = end + 1
})
// The same construction regions-dashboard's UupgsList had, character for character apart from
// variable names, which is what earned it a place in library/ under RULE 2. One fact, one copy.
const highlightedValue = highlightRanges(value, match.indices)
if (String(matchKey).includes('imb_alternate_name')) {
newItem.matches.push({ key: t.value.alternate_name ?? 'Also known as', label: highlightedValue })
} else if (String(matchKey).includes('.')) {

View file

@ -80,12 +80,14 @@ ok(highlightRanges('Cameroon', [[2, 4]]) !== highlightRanges('Cameroon', [[0, 2]
const upg = codeOnly(readFileSync(resolve(ROOT, 'plugin-designs/regions-dashboard/src/UupgsList.vue'), 'utf8'))
ok(/highlightRanges/.test(upg), 'UupgsList routes through highlightRanges()')
ok(!/function escapeHtml\(/.test(upg), 'and no longer hand-rolls escapeHtml (its only callers were inside highlight())')
// people-groups-grid is the SECOND consumer that justified the extraction under RULE 2, and it is NOT
// migrated yet: it is another owner's file, so it is handed over as source rather than self-landed.
// Asserted as not-yet-migrated so this gate states the truth instead of implying the job is finished.
// people-groups-grid is the SECOND consumer that justified the extraction under RULE 2. studio agreed
// to treat it as unowned and asked for it to be cut, so it is migrated now and asserted here.
const pgg = codeOnly(readFileSync(resolve(ROOT, 'plugin-designs/people-groups-grid/people-groups-grid.vue'), 'utf8'))
ok(/search-highlight/.test(pgg),
'people-groups-grid still builds its own highlight (the named second consumer, handed over, not yet migrated)')
ok(/highlightRanges/.test(pgg), 'people-groups-grid routes through highlightRanges() too -- the duplicate is gone')
ok(!/function escapeHtml\(/.test(pgg),
'and its own escapeHtml is gone: after the loop moved, it had ZERO callers left')
ok(!/currentIndex = end \+ 1/.test(pgg),
'the hand-rolled range walk is really removed, not merely shadowed by the import')
console.log(fails ? `\nTEXT-HIGHLIGHT: ${fails} FAIL(s)` : '\nTEXT-HIGHLIGHT: the shared highlighter matches what it replaced, and both escaping deltas are pinned')
process.exit(fails ? 1 : 0)