feat(search): also implement defensive checks for undefined notes

This commit is contained in:
perf3ct 2025-08-10 21:59:20 +00:00
parent 583ab8dc92
commit 5b669ca287
No known key found for this signature in database
GPG Key ID: 569C4EEC436F5232
4 changed files with 49 additions and 19 deletions

View File

@ -57,7 +57,11 @@ class NoteFlatTextExp extends Expression {
const foundAttrTokens: string[] = []; const foundAttrTokens: string[] = [];
for (const token of remainingTokens) { for (const token of remainingTokens) {
if (note.type.includes(token) || note.mime.includes(token)) { // Add defensive checks for undefined properties
const typeMatches = note.type && note.type.includes(token);
const mimeMatches = note.mime && note.mime.includes(token);
if (typeMatches || mimeMatches) {
foundAttrTokens.push(token); foundAttrTokens.push(token);
} }
} }
@ -105,7 +109,11 @@ class NoteFlatTextExp extends Expression {
const foundAttrTokens: string[] = []; const foundAttrTokens: string[] = [];
for (const token of this.tokens) { for (const token of this.tokens) {
if (note.type.includes(token) || note.mime.includes(token)) { // Add defensive checks for undefined properties
const typeMatches = note.type && note.type.includes(token);
const mimeMatches = note.mime && note.mime.includes(token);
if (typeMatches || mimeMatches) {
foundAttrTokens.push(token); foundAttrTokens.push(token);
} }

View File

@ -25,25 +25,25 @@ describe("Progressive Search Strategy", () => {
it("should complete search with exact matches when sufficient results found", () => { it("should complete search with exact matches when sufficient results found", () => {
// Create notes with exact matches // Create notes with exact matches
rootNote rootNote
.child(note("Test Document One")) .child(note("Document Analysis One"))
.child(note("Test Report Two")) .child(note("Document Report Two"))
.child(note("Test Analysis Three")) .child(note("Document Review Three"))
.child(note("Test Summary Four")) .child(note("Document Summary Four"))
.child(note("Test Review Five")) .child(note("Document Overview Five"))
.child(note("Typo Test Documnt")); // This has a typo .child(note("Documnt Analysis Six")); // This has a typo that should require fuzzy matching
const searchContext = new SearchContext(); const searchContext = new SearchContext();
const searchResults = searchService.findResultsWithQuery("test", searchContext); const searchResults = searchService.findResultsWithQuery("document", searchContext);
// Should find 5+ exact matches and not process the typo // Should find 5 exact matches and not need fuzzy matching
expect(searchResults.length).toBeGreaterThanOrEqual(5); expect(searchResults.length).toEqual(5);
// Verify all results have high scores (exact matches) // Verify all results have high scores (exact matches)
const highQualityResults = searchResults.filter(result => result.score >= 10); const highQualityResults = searchResults.filter(result => result.score >= 10);
expect(highQualityResults.length).toBeGreaterThanOrEqual(5); expect(highQualityResults.length).toEqual(5);
// The typo document should not be in results since we have enough exact matches // The typo document should not be in results since we have enough exact matches
expect(findNoteByTitle(searchResults, "Typo Test Documnt")).toBeFalsy(); expect(findNoteByTitle(searchResults, "Documnt Analysis Six")).toBeFalsy();
}); });
it("should use exact match scoring only in Phase 1", () => { it("should use exact match scoring only in Phase 1", () => {

View File

@ -583,16 +583,15 @@ describe("Search", () => {
.child(note("Analysis Report")) // Exact match .child(note("Analysis Report")) // Exact match
.child(note("Data Analysis")) // Exact match .child(note("Data Analysis")) // Exact match
.child(note("Test Analysis")) // Exact match .child(note("Test Analysis")) // Exact match
.child(note("Statistical Analysis")) // Exact match
.child(note("Business Analysis")) // Exact match
.child(note("Advanced Anaylsis")) // Fuzzy match (typo) .child(note("Advanced Anaylsis")) // Fuzzy match (typo)
.child(note("Quick Anlaysis")); // Fuzzy match (typo) .child(note("Quick Anlaysis")); // Fuzzy match (typo)
const searchContext = new SearchContext(); const searchContext = new SearchContext();
const searchResults = searchService.findResultsWithQuery("analysis", searchContext); const searchResults = searchService.findResultsWithQuery("analysis", searchContext);
// Should find all matches but exact ones should come first // With only 3 exact matches (below threshold), fuzzy should be triggered
expect(searchResults.length).toEqual(7); // Should find all 5 matches but exact ones should come first
expect(searchResults.length).toEqual(5);
// Get note titles in result order // Get note titles in result order
const resultTitles = searchResults.map(r => becca.notes[r.noteId].title); const resultTitles = searchResults.map(r => becca.notes[r.noteId].title);
@ -607,7 +606,7 @@ describe("Search", () => {
(title.includes("Anaylsis") || title.includes("Anlaysis")) ? index : -1 (title.includes("Anaylsis") || title.includes("Anlaysis")) ? index : -1
).filter(index => index !== -1); ).filter(index => index !== -1);
expect(exactMatchIndices.length).toEqual(5); expect(exactMatchIndices.length).toEqual(3);
expect(fuzzyMatchIndices.length).toEqual(2); expect(fuzzyMatchIndices.length).toEqual(2);
// CRITICAL: All exact matches must appear before all fuzzy matches // CRITICAL: All exact matches must appear before all fuzzy matches

View File

@ -237,6 +237,19 @@ function findResultsWithExpression(expression: Expression, searchContext: Search
loadNeededInfoFromDatabase(); loadNeededInfoFromDatabase();
} }
// If there's an explicit orderBy clause, skip progressive search
// as it would interfere with the ordering
if (searchContext.orderBy) {
// For ordered queries, don't use progressive search but respect
// the original fuzzy matching setting
return performSearch(expression, searchContext, searchContext.enableFuzzyMatching);
}
// If fuzzy matching is explicitly disabled, skip progressive search
if (!searchContext.enableFuzzyMatching) {
return performSearch(expression, searchContext, false);
}
// Phase 1: Try exact matches first (without fuzzy matching) // Phase 1: Try exact matches first (without fuzzy matching)
const exactResults = performSearch(expression, searchContext, false); const exactResults = performSearch(expression, searchContext, false);
@ -251,7 +264,7 @@ function findResultsWithExpression(expression: Expression, searchContext: Search
return exactResults; return exactResults;
} }
// Phase 2: Add fuzzy matching as fallback // Phase 2: Add fuzzy matching as fallback when exact matches are insufficient
const fuzzyResults = performSearch(expression, searchContext, true); const fuzzyResults = performSearch(expression, searchContext, true);
// Merge results, ensuring exact matches always rank higher than fuzzy matches // Merge results, ensuring exact matches always rank higher than fuzzy matches
@ -402,6 +415,16 @@ function findResultsWithQuery(query: string, searchContext: SearchContext): Sear
return []; return [];
} }
// If the query starts with '#', it's a pure expression query.
// Don't use progressive search for these as they may have complex
// ordering or other logic that shouldn't be interfered with.
const isPureExpressionQuery = query.trim().startsWith('#');
if (isPureExpressionQuery) {
// For pure expression queries, use standard search without progressive phases
return performSearch(expression, searchContext, searchContext.enableFuzzyMatching);
}
return findResultsWithExpression(expression, searchContext); return findResultsWithExpression(expression, searchContext);
} }