@@ -113,7 +113,19 @@ 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 => {
122+ const words = part . split ( / \s + / ) ;
123+ return words . some ( word => word === normalizedPhrase ) ;
124+ } ) ;
125+ } else {
126+ // Multi-word phrase: check for substring match
127+ matches = normalizedFlatText . includes ( `=${ normalizedPhrase } ` ) ;
128+ }
117129
118130 if ( ( this . operator === "=" && matches ) || ( this . operator === "!=" && ! matches ) ) {
119131 resultNoteSet . add ( noteFromBecca ) ;
@@ -155,7 +167,15 @@ class NoteContentFulltextExp extends Expression {
155167 // Join tokens with single space to form the phrase
156168 const phrase = normalizedTokens . join ( " " ) ;
157169
158- // Check if the phrase appears as a substring (consecutive words)
170+ // For single-word phrases, use word-boundary matching to avoid substring matches
171+ // e.g., "asd" should not match "asdfasdf"
172+ if ( ! phrase . includes ( ' ' ) ) {
173+ // Single word: split into words and check for exact match
174+ const words = normalizedContent . split ( / \s + / ) ;
175+ return words . some ( word => word === phrase ) ;
176+ }
177+
178+ // For multi-word phrases, check if the phrase appears as consecutive words
159179 if ( normalizedContent . includes ( phrase ) ) {
160180 return true ;
161181 }
0 commit comments