@@ -113,7 +113,16 @@ class NoteContentFulltextExp extends Expression {
113113 const normalizedFlatText = normalizeSearchText ( flatText ) ;
114114
115115 // Check if =phrase appears in flatText (indicates attribute value match)
116- matches = normalizedFlatText . includes ( `=${ normalizedPhrase } ` ) ;
116+ // For single words, use word-boundary matching to avoid substring matches
117+ if ( ! normalizedPhrase . includes ( ' ' ) ) {
118+ // Single word: look for =word with word boundaries
119+ // Split by = to get attribute values, then check each value for exact word match
120+ const parts = normalizedFlatText . split ( '=' ) ;
121+ matches = parts . slice ( 1 ) . some ( part => this . exactWordMatch ( normalizedPhrase , part ) ) ;
122+ } else {
123+ // Multi-word phrase: check for substring match
124+ matches = normalizedFlatText . includes ( `=${ normalizedPhrase } ` ) ;
125+ }
117126
118127 if ( ( this . operator === "=" && matches ) || ( this . operator === "!=" && ! matches ) ) {
119128 resultNoteSet . add ( noteFromBecca ) ;
@@ -124,6 +133,17 @@ class NoteContentFulltextExp extends Expression {
124133 return resultNoteSet ;
125134 }
126135
136+ /**
137+ * Helper method to check if a single word appears as an exact match in text
138+ * @param wordToFind - The word to search for (should be normalized)
139+ * @param text - The text to search in (should be normalized)
140+ * @returns true if the word is found as an exact match (not substring)
141+ */
142+ private exactWordMatch ( wordToFind : string , text : string ) : boolean {
143+ const words = text . split ( / \s + / ) ;
144+ return words . some ( word => word === wordToFind ) ;
145+ }
146+
127147 /**
128148 * Checks if content contains the exact word (with word boundaries) or exact phrase
129149 * This is case-insensitive since content and token are already normalized
@@ -139,9 +159,8 @@ class NoteContentFulltextExp extends Expression {
139159 return normalizedContent . includes ( normalizedToken ) ;
140160 }
141161
142- // For single words, split content into words and check for exact match
143- const words = normalizedContent . split ( / \s + / ) ;
144- return words . some ( word => word === normalizedToken ) ;
162+ // For single words, use exact word matching to avoid substring matches
163+ return this . exactWordMatch ( normalizedToken , normalizedContent ) ;
145164 }
146165
147166 /**
@@ -155,7 +174,14 @@ class NoteContentFulltextExp extends Expression {
155174 // Join tokens with single space to form the phrase
156175 const phrase = normalizedTokens . join ( " " ) ;
157176
158- // Check if the phrase appears as a substring (consecutive words)
177+ // For single-word phrases, use word-boundary matching to avoid substring matches
178+ // e.g., "asd" should not match "asdfasdf"
179+ if ( ! phrase . includes ( ' ' ) ) {
180+ // Single word: use exact word matching to avoid substring matches
181+ return this . exactWordMatch ( phrase , normalizedContent ) ;
182+ }
183+
184+ // For multi-word phrases, check if the phrase appears as consecutive words
159185 if ( normalizedContent . includes ( phrase ) ) {
160186 return true ;
161187 }
0 commit comments