From 2333ac4f5dc2bfbfff2c76969309d15ea977c64e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 13 Oct 2026 14:30:14 -0700 Subject: [PATCH] fix(search): fold -ie plurals in the Jira partial-match term key The singular key mapped -ies to -y, so movies keyed to 'movy' while movie stayed 'movie' and the half-the-terms floor missed a pair Jira's stemmed search treats as one word. As in the Porter stemmer, a final -y and -ie(s) now fold to i (policy/policies -> polici, movie/movies -> movi), which also makes the separate -ies branch unnecessary. --- apps/sim/lib/sim-search/live/text-match.test.ts | 5 +++++ apps/sim/lib/sim-search/live/text-match.ts | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/apps/sim/lib/sim-search/live/text-match.test.ts b/apps/sim/lib/sim-search/live/text-match.test.ts index 928d830d37d..c9c9bf78d99 100644 --- a/apps/sim/lib/sim-search/live/text-match.test.ts +++ b/apps/sim/lib/sim-search/live/text-match.test.ts @@ -79,6 +79,11 @@ describe('text match clauses', () => { expect(holdsHalfTheTerms('databases releases checklist owner')('Database release')).toBe(true) }) + it('folds -ies plurals of both -y and -ie words, as the Porter stemmer does', () => { + expect(holdsHalfTheTerms('movies rotation schedule owner')('Movie rotation')).toBe(true) + expect(holdsHalfTheTerms('policies review schedule owner')('Policy review')).toBe(true) + }) + it('matches a term with punctuation inside it as written', () => { expect(holdsHalfTheTerms('ci/cd pipeline failure owner')('CI/CD pipeline')).toBe(true) }) diff --git a/apps/sim/lib/sim-search/live/text-match.ts b/apps/sim/lib/sim-search/live/text-match.ts index a4a11419e24..2f5ed21fbdf 100644 --- a/apps/sim/lib/sim-search/live/text-match.ts +++ b/apps/sim/lib/sim-search/live/text-match.ts @@ -72,12 +72,16 @@ function distinctTerms(query: string, keyOf: (term: string) => string): string[] .slice(0, MAX_ANY_TERMS) } -/** One key for a word and its regular plural (`token`/`tokens`, `process`/`processes`). */ +/** + * One key for a word and its regular plural (`token`/`tokens`, `process`/`processes`). As in the + * Porter stemmer, a final `-y` and `-ie(s)` fold to `i`, so `policy`/`policies` and + * `movie`/`movies` each share a key. + */ function singularKey(term: string): string { const key = term.toLowerCase() - if (key.length > 4 && key.endsWith('ies')) return `${key.slice(0, -3)}y` - const stem = /[^su]s$/.test(key) ? key.slice(0, -1) : key - return /(?:s|x|z|ch|sh)e$/.test(stem) ? stem.slice(0, -1) : stem + const stripped = /[^su]s$/.test(key) ? key.slice(0, -1) : key + const stem = /(?:s|x|z|ch|sh|i)e$/.test(stripped) ? stripped.slice(0, -1) : stripped + return stem.replace(/y$/, 'i') } /** Escapes a value for a quoted JQL or CQL string, treating Lucene operators as literal text. */