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);
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 regex = new RegExp(escaped, m.caseSensitive ? 'g' : 'gi');
let match;
@@ -70,7 +70,7 @@
let result = text;
const sorted = [...maps].sort((a, b) => b.substitute.length - a.substitute.length);
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 regex = new RegExp(escaped, m.caseSensitive ? 'g' : 'gi');
result = result.replace(regex, m.real);
@@ -1130,6 +1130,7 @@
for (const p of pairs) {
const searchTerm = settings.revealMode ? p.to : p.from;
if (!searchTerm?.trim()) continue;
const escaped = esc(searchTerm);
// Add word boundaries when the term starts/ends with word chars to
// prevent partial-word matches (e.g. "aud" inside "Claude")
+4 -4
View File
@@ -21,7 +21,7 @@ const SubstitutionEngine = {
);
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 regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
@@ -54,7 +54,7 @@ const SubstitutionEngine = {
);
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 regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
@@ -71,7 +71,7 @@ const SubstitutionEngine = {
const found = [];
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 regex = new RegExp(escaped, mapping.caseSensitive ? 'g' : 'gi');
@@ -103,7 +103,7 @@ const SubstitutionEngine = {
// Collect all match positions in the original text
const matches = [];
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 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);
}
// Add mappings
// Add mappings — skip any with blank/whitespace-only real values
for (const m of result.mappings) {
if (!m.real?.trim()) continue;
await Storage.addMapping({
real: m.real,
substitute: m.substitute || '',
real: m.real.trim(),
substitute: m.substitute?.trim() || '',
category: m.category || 'general',
caseSensitive: false,
});