Fix whitespace-only mappings highlighting every space on the page

A mapping imported with real: " " (single space) passed the !m.real
guard (space is truthy), producing new RegExp(" ", "gi") which matched
every space character in the DOM and highlighted them all.

- Added .trim() to all real/substitute guard conditions in content.js
  (inline substitute, reveal functions) and substitution-engine.js so
  whitespace-only values are treated as empty and skipped
- Added the same guard in highlightMatches so the CSS Highlight API
  never creates ranges for blank search terms
- Added trim + blank-check to the bulk import path in options.js so
  whitespace-only real values are dropped at import time rather than
  saved to storage

https://claude.ai/code/session_01CwcZK8nqL8pyBH9AxDs9qo
This commit is contained in:
Claude
2026-04-02 13:19:14 +00:00
parent abeb79a93f
commit 3d8642db52
3 changed files with 11 additions and 9 deletions
+3 -2
View File
@@ -50,7 +50,7 @@
const sorted = [...maps].sort((a, b) => b.real.length - a.real.length); const sorted = [...maps].sort((a, b) => b.real.length - a.real.length);
for (const m of sorted) { for (const m of sorted) {
if (!m.enabled || !m.real || !m.substitute) continue; if (!m.enabled || !m.real?.trim() || !m.substitute?.trim()) continue;
const escaped = esc(m.real); const escaped = esc(m.real);
const regex = new RegExp(escaped, m.caseSensitive ? 'g' : 'gi'); const regex = new RegExp(escaped, m.caseSensitive ? 'g' : 'gi');
let match; let match;
@@ -70,7 +70,7 @@
let result = text; let result = text;
const sorted = [...maps].sort((a, b) => b.substitute.length - a.substitute.length); const sorted = [...maps].sort((a, b) => b.substitute.length - a.substitute.length);
for (const m of sorted) { for (const m of sorted) {
if (!m.enabled || !m.real || !m.substitute) continue; if (!m.enabled || !m.real?.trim() || !m.substitute?.trim()) continue;
const escaped = esc(m.substitute); const escaped = esc(m.substitute);
const regex = new RegExp(escaped, m.caseSensitive ? 'g' : 'gi'); const regex = new RegExp(escaped, m.caseSensitive ? 'g' : 'gi');
result = result.replace(regex, m.real); result = result.replace(regex, m.real);
@@ -1130,6 +1130,7 @@
for (const p of pairs) { for (const p of pairs) {
const searchTerm = settings.revealMode ? p.to : p.from; const searchTerm = settings.revealMode ? p.to : p.from;
if (!searchTerm?.trim()) continue;
const escaped = esc(searchTerm); const escaped = esc(searchTerm);
// Add word boundaries when the term starts/ends with word chars to // Add word boundaries when the term starts/ends with word chars to
// prevent partial-word matches (e.g. "aud" inside "Claude") // prevent partial-word matches (e.g. "aud" inside "Claude")
+4 -4
View File
@@ -21,7 +21,7 @@ const SubstitutionEngine = {
); );
for (const mapping of sorted) { for (const mapping of sorted) {
if (!mapping.enabled || !mapping.real || !mapping.substitute) continue; if (!mapping.enabled || !mapping.real?.trim() || !mapping.substitute?.trim()) continue;
const escaped = this._escapeRegex(mapping.real); const escaped = this._escapeRegex(mapping.real);
const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi'); const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
@@ -54,7 +54,7 @@ const SubstitutionEngine = {
); );
for (const mapping of sorted) { for (const mapping of sorted) {
if (!mapping.enabled || !mapping.real || !mapping.substitute) continue; if (!mapping.enabled || !mapping.real?.trim() || !mapping.substitute?.trim()) continue;
const escaped = this._escapeRegex(mapping.substitute); const escaped = this._escapeRegex(mapping.substitute);
const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi'); const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
@@ -71,7 +71,7 @@ const SubstitutionEngine = {
const found = []; const found = [];
for (const mapping of mappings) { for (const mapping of mappings) {
if (!mapping.enabled || !mapping.real) continue; if (!mapping.enabled || !mapping.real?.trim()) continue;
const escaped = this._escapeRegex(mapping.real); const escaped = this._escapeRegex(mapping.real);
const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi'); const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
@@ -103,7 +103,7 @@ const SubstitutionEngine = {
// Collect all match positions in the original text // Collect all match positions in the original text
const matches = []; const matches = [];
for (const mapping of sorted) { for (const mapping of sorted) {
if (!mapping.enabled || !mapping.real || !mapping.substitute) continue; if (!mapping.enabled || !mapping.real?.trim() || !mapping.substitute?.trim()) continue;
const escaped = this._escapeRegex(mapping.real); const escaped = this._escapeRegex(mapping.real);
const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi'); const regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
+4 -3
View File
@@ -1814,11 +1814,12 @@ async function applyBulkImport() {
await Storage.updateProfile(profile.id, profile); await Storage.updateProfile(profile.id, profile);
} }
// Add mappings // Add mappings — skip any with blank/whitespace-only real values
for (const m of result.mappings) { for (const m of result.mappings) {
if (!m.real?.trim()) continue;
await Storage.addMapping({ await Storage.addMapping({
real: m.real, real: m.real.trim(),
substitute: m.substitute || '', substitute: m.substitute?.trim() || '',
category: m.category || 'general', category: m.category || 'general',
caseSensitive: false, caseSensitive: false,
}); });