Skip to content

feat(platform): let chat find entities by words in a question - #3123

Merged
Israeltheminer merged 1 commit into
mainfrom
feat/chat-legs-word-search
Sep 3, 2026
Merged

feat(platform): let chat find entities by words in a question#3123
Israeltheminer merged 1 commit into
mainfrom
feat/chat-legs-word-search

Conversation

@Israeltheminer

Copy link
Copy Markdown
Collaborator

Chat can now find a task, project, contact, product or knowledge entry from a question. Before this, every entity leg compared the user's whole message as one substring, so anything phrased as a question matched nothing.

Closes #2992. Supersedes #3106, which fixed three of these legs against the Convex tree that #3107 replaced.

Why

Each leg receives the user's message as its search term and passes it straight to ILIKE '%<term>%'. Observed against a real Postgres, with one product and one task seeded:

Term Rows on origin/main With this change
do we have red running shoes 0 1
Red Running 1 1
recruitment ads Facebook ad account project tasks 0 1
who is anna lee 0 1

The second row is the one that made this invisible: a typed name fragment works, so the legs look healthy until someone asks a question. The third is the query that opened #2992 — a board question that answered with nothing, which is why chat recommended Asana and Jira instead.

#2983 fixed this on the Convex entity search. The 0.5 legs are new SQL and were written with the phrase compare, so the fix did not carry across.

What changed

wordStartPatterns in backend/lib/word-match.ts turns a term into Postgres word-start patterns. It imports queryTokens from the 0.4 matcher rather than re-listing the en/de/fr stopword set, so the two cannot drift.

Each leg gains OR column ~* ANY(patterns) next to its existing ILIKE. Fields follow the 0.4 strategy files: tasks on title and description, projects on name and description, contacts on name and email.

Two rules make a question usable. Function words and one-character fragments are dropped, so a term with no signal yields no patterns and the leg adds no word clause. A token must match at the start of a word, so an OR over tokens does not turn ad into a match for overhead.

The query is split on anything that is not a letter or digit, matching how the 0.4 matcher defines a word on the text side. Without that, a trailing question mark rides on the last token and have? reads as neither a stopword nor a prefix of anything.

Knowledge entries take an opt-in flag, because listKnowledgeEntries also backs the entries page, which passes what a reader typed rather than a question.

Risk

Word matching is added, never substituted. Anyone replacing a phrase clause with the word clause would drop the mid-word matches the pages rely on.

Tests

word-match.test.ts covers the token rules. Three mutations, each turning it red: dropping the punctuation split (2 failed), using all mode instead of any (5 failed), and dropping the \m anchor (5 failed).

integration-check.ts gains a leg check over real SQL, in a fixture org of its own so its rows do not move the org-wide task counts three later checks assert. It pins the table above plus two negatives: an unrelated question and an all-stopword question both return nothing, and do we have any ebook tasks does not reach Facebook while do we have any facebook tasks does.

Scope

Ranking is unchanged. This decides whether a row matches, not how matches are ordered — the legs still order by updated_at_ms.

avez is not in the shared French stopword list, so it survives tokenization. A surplus token is harmless under an OR of word starts, and widening the list would change the 0.4 legs that share it.

Gate: typecheck, oxlint --type-aware, oxfmt --check, knip and lint:sast green; the new unit test 7/7.

Every chat entity leg receives the user's whole message as its search
term and compares it as one substring, so anything phrased as a question
matches nothing. "do we have red running shoes" found no product; the
board question that opened #2992 found no task.

Adds a word clause alongside each leg's existing phrase clause: the
meaningful words of the term, each matched at the START of a word. The
phrase clause is untouched, so a typed name fragment keeps working.

Closes #2992.
@Israeltheminer
Israeltheminer force-pushed the feat/chat-legs-word-search branch from 45e445f to 9e99db0 Compare September 2, 2026 14:47
@Israeltheminer
Israeltheminer merged commit 58c3de3 into main Sep 3, 2026
48 checks passed
@Israeltheminer
Israeltheminer deleted the feat/chat-legs-word-search branch September 3, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: chat cannot see the organization's work — tasks, projects, conversations

1 participant