feat: wire chat search box to full-text search by DanielleMaywood · Pull Request #27973 · coder/coder · GitHub
Skip to content

feat: wire chat search box to full-text search - #27973

Merged
DanielleMaywood merged 13 commits into
mainfrom
danielle/codagt-726-chat-search-ui-search-box-wiring
Aug 12, 2026
Merged

feat: wire chat search box to full-text search#27973
DanielleMaywood merged 13 commits into
mainfrom
danielle/codagt-726-chat-search-ui-search-box-wiring

Conversation

@DanielleMaywood

@DanielleMaywood DanielleMaywood commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Wires the Coder Agents chat search box up to the backend full-text search filter. Bare free text previously produced a title:"..." substring filter; it now produces a search: filter, so free text matches chat titles, PR titles, PR numbers, and message bodies via the FTS index added in #27126.

The box is for text; structured filters are pills. Free text is wrapped in a single search:"..." token (the backend query tokenizer requires a single token and has no escape handling for embedded quotes, so embedded " are stripped first). Operators stay live, Google-style: fix OR bug disjoins, fix -timeout excludes. One limitation: quoted phrases are flattened to AND-of-words, because the backend tokenizer cannot carry embedded quotes through to websearch_to_tsquery. Typing a recognized filter code (has_unread:true, archived:true, pr_status:open, diff_url:<url>) pulls it out of the text into a removable pill. Typed title: is no longer special-cased; it is literal search text and never triggers the search/title mutual-exclusion 400.

A search whose text tokenizes to zero lexemes (operator-only or punctuation-only input) is not an error: the backend returns an empty list and the UI shows the no-results state. Pill values are validated (enum, boolean, URL) so a committed pill is always a query fragment the backend accepts.

Refs CODAGT-726
Depends on #27126

Implemented by Coder Agents, reviewed and tested by a human.

Implementation notes
  • searchQuery.ts: pure helpers build the wire query directly from structured state. buildChatSearchQuery(filters, freeText) returns { query, hasSearchText } and never inspects free text for key:value. extractTypedFilters is the quote-aware typed-filter-to-pill seam. Per-key validators (CHAT_SEARCH_FILTER_VALIDATORS) gate pill creation.
  • ChatSearchDialog.tsx: debounces a single primitive (a JSON { query, hasSearchText } snapshot) so the debounce does not depend on React Compiler object memoization. Typed-filter extraction runs only when the caret is at the end, preserves separators, and upserts a typed filter whose key already has a pill (last-write-wins).
  • Backend (coderd/exp_chats.go): the zero-lexeme ChatSearchQueryIsEmpty pre-check is removed; a zero-lexeme search matches nothing naturally, so the handler returns an empty list. The querier/dbauthz/dbmock/dbmetrics surface for that query is removed too (make gen).
  • ChatSearchResults.tsx: the empty-state indexing note only appears when a search: token was emitted (hasSearchText), and stale errors are cleared once the search becomes inactive.

Contract coverage:

  • coderd/searchquery/search_test.go TestSearchChatsFrontendEmitted asserts searchquery.Chats accepts every query shape the frontend emits (cross-referenced with the frontend emitters).
  • site/src/api/queries/chats.test.ts adds shape tests for the sidebar list emitter (getChatListQueryString).
  • site/e2e/tests/agents/chatSearch.spec.ts is a Playwright smoke test that logs in, opens /agents, searches, and asserts the search:"..." request returns 200 with the no-results state and no error alert.

Tests: 195 frontend unit tests, 30 Storybook interaction stories, Go contract + handler tests, and the e2e smoke test all pass.

…arch

The Coder Agents chat search dialog sent bare free text as a title
substring filter (title:"..."). Point it at the backend full-text
search filter (search:) so free text matches chat titles, PR titles,
PR numbers, and message bodies.

Bare free text is wrapped in a quoted phrase by default, since the
backend query tokenizer requires the search value to be a single
token. Websearch operators (quoted phrases, OR, -negation) still pass
through when the user supplies a proper quoted phrase. The empty
state notes that message content is indexed periodically.
@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

@codex review

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review model:kimi-k3 thinking:xhigh

@coder-agents-review

coder-agents-review Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Chat: Review posted | View chat
Requested: 2026-08-11 08:47 UTC by @DanielleMaywood

Review history
  • R1 (2026-08-10): 11 reviewers, 2 Nit, 2 Note, 5 P2, 5 P3, REQUEST_CHANGES. Review
  • R2 (2026-08-10): 11 reviewers, 2 Nit, 4 Note, 7 P2, 13 P3, REQUEST_CHANGES. Review
  • R3 (2026-08-10), 2 Nit, 4 Note, 7 P2, 13 P3, COMMENT. Review
  • R4 (2026-08-10): 13 reviewers, 4 Nit, 9 Note, 7 P2, 24 P3, COMMENT. Review
  • R5 (2026-08-11), 4 Nit, 9 Note, 7 P2, 24 P3, COMMENT. Review
  • R6 (2026-08-11): 13 reviewers, 4 Nit, 9 Note, 7 P2, 25 P3, COMMENT. Review

deep-review v0.9.0 | Round 6 | 19fdc23..1fcef68

Last posted: Round 6, 45 findings (7 P2, 25 P3, 4 Nit, 9 Note), COMMENT. Review

Finding inventory

Finding inventory, PR #27973

Findings

# Sev Status Location Summary Round Reviewer Posted
CRF-1 P2 Author fixed (2ee34e5) searchQuery.ts:175 Quote-only or punctuation-only free text emits a search value the backend rejects with 400; old title path was benign R1 Hisoka P2, Mafuuu P2, Nami P2, Netero P3, Bisky P3, Pariston P3, Leorio P3, Komugi P3, Meruem P3 Yes
CRF-2 P2 Author fixed (2ee34e5) searchQuery.test.ts:96-98 Test name/comment claim operators are searched as literal text; OR and -negation stay live into websearch_to_tsquery (verified on Postgres) R1 Leorio P2, Pariston P2, Hisoka P3, Mafuuu P3, Komugi P3, Nami P3, Meruem P3 Yes
CRF-3 P2 Author fixed (2ee34e5) searchQuery.ts:111 Doc comment and passthrough-set comment claim title: always merges into search:; a lone title: passes through unchanged R1 Gon P2, Leorio P3, Mafuuu Nit Yes
CRF-4 P2 Author fixed (2ee34e5) searchQuery.ts:140 Title-fold comment justifies the merge with the wrong backend constraint (repeated parameter; actual constraint is search/title mutual exclusion) R1 Mafu-san P2 Yes
CRF-5 P2 Author fixed (86445a0) searchQuery.test.ts:96 Vault-documented lesson from PR #27375 unapplied: tests assert only emitted string shape, no mechanical guard that searchquery.Chats accepts them R1 Mafu-san P2, Meruem Note Yes
CRF-6 P3 Author contested; panel closed R2 (7/7 accept) searchQuery.ts:147 User-typed search:foo is treated as bare text and re-wrapped as search:"search:foo", ANDing the word "search" into the query R1 Meruem P3, Bisky Note Yes
CRF-7 P3 Author fixed (2ee34e5) searchQuery.ts:174 Merge branch returns passthroughFilters, non-merge returns normalizedTokens; correctness depends on unstated invariant that they differ only by title tokens R1 Meruem P3 Yes
CRF-8 P3 Author fixed (2ee34e5) ChatSearchResults.tsx:200 Indexing-lag copy shows for every empty result, including filter-only queries where FTS never ran R1 Mafuuu P3, Leorio P3, Meruem P3, Bisky Note, Hisoka Note, Pariston Note, Gon Note, Nami Note, Komugi Note Yes
CRF-9 P3 Author fixed (2ee34e5) searchQuery.ts:143 Explicit title: filter silently widens to full-text scope when bare text or a second title: appears R1 Mafuuu P3, Hisoka Note, Pariston Note, Meruem Note Yes
CRF-10 P3 Author fixed (2ee34e5) searchQuery.ts:158 "Backend rejects a repeated parameter" rationale stated three times in one file; quote rationale also repeated three times R1 Gon P3 Yes
CRF-11 Nit Author fixed (2ee34e5) searchQuery.ts:127 hasBareSearchText is true when there is no bare search text (title:Fix title:Race); rename to emitSearchFilter R1 Gon Nit Yes
CRF-12 Nit Author fixed (2ee34e5) searchQuery.ts:169 Comment lists what search: matches but omits exact PR-number matching for numeric values R1 Leorio Nit Yes
CRF-13 P4 Dropped by orchestrator (Gon comment-bloat class collapsed; one representative comment posted) searchQuery.ts:1 sanitizeChatSearchValue header spends two sentences on one why R1 Gon P2 No
CRF-14 P4 Dropped by orchestrator (same class as CRF-13) searchQuery.ts:168 Free-text comment restates quote-stripping rationale owned by sanitizeChatSearchValue R1 Gon P2 No
CRF-15 P4 Dropped by orchestrator (same class as CRF-13) ChatSearchDialog.tsx:134 buildQuery comment narrates visible mechanism and restates callee contract R1 Gon P2 No
CRF-16 P4 Dropped by orchestrator (same class as CRF-13) searchQuery.test.ts:82 Test comment restates the expectations below it R1 Gon P2 No
CRF-17 P4 Dropped by orchestrator (same class as CRF-13) searchQuery.test.ts:105 Trailing clause restates the assertion R1 Gon P2 No
CRF-18 Note Panel closed R2 (accepted, same uniformity rule as CRF-6) searchQuery.ts:17 Backend-supported filters pr:, repo:, pr_title:, source: are still swallowed into free text; extending the set collides with the same merge problem R1 Mafuuu Note Yes
CRF-19 Note Author fixed (2ee34e5) searchQuery.ts:141 Class fix one layer down: let the backend accept search AND title together, deleting the fold and the semantic inconsistency; needs a human decision/ticket R1 Meruem Note Yes
CRF-20 P2 Author fixed (86445a0) ChatSearchDialog.tsx:153 queryInput fresh object literal every render fed to useDebouncedValue; uncompiled this is a perpetual 500ms render loop plus debounce starvation; correctness rests on React Compiler memoization R2 Komugi P1, Netero P2, Pariston P2, Leorio P2, Takumi P2, Hisoka Note, Mafuuu Note, Nami Note, Meruem Note Yes
CRF-21 P2 Author fixed (86445a0) searchQuery.test.ts:108 "Preserves websearch operators" overclaims: the fixture's own quoted-phrase operator is destroyed (phrase adjacency impossible from the UI); comment and PR description should state the limitation R2 Mafuuu P2, Bisky P3 Yes
CRF-22 P3 Author fixed (86445a0) searchQuery.ts:40 Letter/number guard is an incomplete fix for the CRF-1 class: operator-only text (or) passes the guard, tokenizes to an empty tsquery, and 400s R2 Leorio P3 Yes
CRF-23 P3 Author fixed (86445a0) searchQuery.ts:131 Typed filter whose key is already active is consumed and silently discarded (has_unread:false with true pill active keeps the inverse filter) R2 Hisoka P3, Meruem P3 Yes
CRF-24 P3 Author fixed (3132d51) ChatSearchDialog.tsx:208 Pill whose value sanitizes to empty is committed, rendered as active, and silently dropped from the query; dialog shows unfiltered recent chats under an apparently-active pill R2 Nami P3, Hisoka P3, Mafuuu P3, Netero Note Yes
CRF-25 P3 Author fixed (86445a0) ChatSearchDialog.tsx:289 Space-triggered extraction swallows the typed space when the consumed token is not last; next keystroke glues onto the preceding word; mid-string cursor also teleports R2 Netero P3, Mafuuu P3, Nami P3 Yes
CRF-26 P3 Author fixed (86445a0) searchQuery.test.ts:50 Suite blesses pr_status:"open merged", which the backend rejects (wants comma-separated); live instance of the CRF-5 class R2 Mafu-san P3 Yes
CRF-27 P3 Author fixed (86445a0) searchQuery.test.ts:108 No test pins first-colon splitting for extractTypedFilters, so the colon-containing diff_url value is an unguarded live mutant R2 Bisky P3 Yes
CRF-28 P3 Author fixed (86445a0) searchQuery.ts:95 extractTypedFilters export has no doc comment; consumed-without-filter, unbalanced-quote passthrough, and trailing-space contract are unstated R2 Leorio P3 Yes
CRF-29 P3 Author acknowledged R4 (corrected the record on the thread: "acknowledged / by design") PRRC_kwDOGkVX1s7fc8Nh CRF-6 reply opens "Fixed." while the flagged behavior is retained and defended; misleading thread label R2 Mafu-san P3 Yes
CRF-30 P4 Dropped by orchestrator (Gon comment-bloat class collapsed; one representative comment posted) ChatSearchDialog.tsx:52 Comment restates the derivation visible on the next line R2 Gon P2 No
CRF-31 P4 Dropped by orchestrator (same class as CRF-30) ChatSearchDialog.tsx:151 First sentence narrates the mechanism; only the second earns its line R2 Gon P2 No
CRF-32 P4 Dropped by orchestrator (same class as CRF-30) ChatSearchDialog.tsx:288 New code uses (previous)/(filter) where the file uses (prev)/(f) R2 Gon Nit No
CRF-33 P4 Dropped by orchestrator (deferral wording, not a finding; ticket need folded into CRF-5 comment) ChatSearchDialog.stories.tsx:21 Disclosed mockChat follow-up has no ticket R2 Mafu-san P4 No
CRF-34 Note Author fixed (86445a0) ChatSearchDialog.tsx:127 const buildQuery = buildChatSearchQuery is a single-use alias with a comment narrating the deleted parser R2 Gon P2, Nami Nit, Meruem Nit, Netero Note Yes
CRF-35 Note Author acknowledged R3 (symmetric debounce on deletion is deliberate; converges in 500ms, keeps snapshot atomic) ChatSearchDialog.tsx:158 Clearing free text with a pill active leaves the stale search: token for one debounce period; converges, defensible R2 Takumi Note Yes
CRF-36 P3 Author fixed (3132d51) exp_chats.go:395 ChatSearchQueryIsEmpty pre-check is redundant with the main query (zero-lexeme tsquery matches nothing), costs a DB round trip on every search, and short-circuits before label validation R4 Netero P2, Pariston P3, Meruem P3, Mafuuu P3 Yes
CRF-37 P3 Author fixed (3132d51) ChatSearchDialog.tsx:160 querySnapshot "1"/"0" flag-prefix encoding is uncommented stringly-typed tuple; slice(1)/startsWith("1") undebuggable without the rationale R4 Gon P3, Leorio P3, Chopper P3, Meruem P3, Hisoka Note, Mafu-san Nit, Kite Nit Yes
CRF-38 P3 Author fixed (3132d51) searchQuery.ts:153 Filter pill values are not validated (pr_status:banana, has_unread:maybe, archived:yes emit and 400 against the backend enum/bool validation) R4 Meruem P3, Mafu-san P3 Yes
CRF-39 P3 Author fixed (3132d51) searchQuery.ts:170 needsTrailingSeparator and its doc contract are dead at the only call site (caller trimEnds and re-decides); split ownership R4 Meruem P3, Mafuuu Note, Gon Nit, Leorio Nit Yes
CRF-40 P3 Author fixed (3132d51) ChatSearchDialog.stories.tsx:730 Negative getChats assertion in EmptyIncompleteFilterDoesNotCommit runs before the debounce window and asserts an unreachable call; dead under every schedule (mutation-verified) R4 Komugi P3, Bisky P3 Yes
CRF-41 P3 Author fixed (3132d51) search_test.go:1291 Contract table misses the live sidebar emitter shape archived:false has_unread:true (chatStatus !== "all") R4 Bisky P3 Yes
CRF-42 P3 Author fixed (3132d51) searchQuery.test.ts:148 No test feeds extractTypedFilters two distinct keys; the plural merge path is unexercised R4 Bisky P3 Yes
CRF-43 P3 Author fixed (3132d51) ChatSearchDialog.stories.tsx:768 NoSearchableWordsShowsNoResults names a scenario its input does not produce ("or" is a searchable lexeme under simple config, verified on Postgres) R4 Netero P3, Meruem Note Yes
CRF-44 P3 Author fixed (3132d51) exp_chats_test.go:2068 Backend zero-lexeme test pins !!!, which the frontend guard blocks, and skips or, the guard-passing input the commit message names R4 Chopper P3 Yes
CRF-45 P3 Author fixed (3132d51) PR description Implementation notes still describe the deleted 400-to-empty-state mapping after 026020b R4 Mafu-san P3, Leorio P3, Netero Nit, Pariston Nit, Mafuuu Nit Yes
CRF-46 P3 Author fixed (3132d51) searchQuery.ts:13 Quoting rationale annotates normalizeChatSearchFilterValue, which does no quoting; the wrapping lives in formatChatSearchFilterToken R4 Gon P3, Leorio Nit Yes
CRF-47 P4 Dropped by orchestrator (Gon comment-bloat class; folded as one representative bullet into CRF-46 comment) search_test.go:1273 Precedent-citation sentence in the contract test comment is bloat R4 Gon P2 No
CRF-48 P4 Dropped by orchestrator (same class as CRF-47) ChatSearchDialog.tsx:167 normalizedQuery is a stale name; rename to debouncedQuery R4 Gon Nit No
CRF-49 P4 Dropped by orchestrator (same class as CRF-47) ChatSearchDialog.stories.tsx:37 MockChat spread forces summary/created_at literals to be restated three times R4 Netero Nit No
CRF-50 Nit Author fixed (3132d51; query and comment deleted with the pre-check) chats.sql:344 ChatSearchQueryIsEmpty comment still says "Used to reject input" but the sole caller now short-circuits to 200 empty R4 Hisoka Nit, Mafuuu Nit, Komugi Nit Yes
CRF-51 Nit Author fixed (1fcef68) exp_chats.go:360 Swagger q param documents search: but not the silent-empty zero-lexeme behavior R4 Leorio Nit Yes
CRF-52 Note Author fixed (3132d51) searchQuery.ts:57 Punctuation-only text (guard-suppressed, DefaultView) and operator-only text (200 empty, No matching chats) land in different empty states R4 Hisoka P3, Nami Note Yes
CRF-53 Note Author fixed (3132d51) searchQuery.test.ts:4 knownKeys in tests hardcodes the four filter keys instead of sharing KNOWN_FILTER_KEYS; a fifth pill leaves the test stale R4 Netero Note Yes
CRF-54 Note Author fixed (3132d51) chatSearch.spec.ts:27 not.toBeVisible() alert assertion resolves instantly; adds no signal beyond the 200 check R4 Bisky Note, Mafuuu Note, Chopper Note Yes
CRF-55 Note Author fixed (3132d51) ChatSearchResults.tsx:51 Error branch outranks default view during debounce lag; a failed search's alert lingers ~500ms after clearing R4 Nami Note Yes
CRF-56 Note Panel re-raised R4 (the OR exclusion in hasSearchText rests on a false premise: lone OR tokenizes to the lexeme 'or', same as lowercase; the note is suppressed exactly where it applies) ChatSearchResults.tsx:209 For operator-only queries the indexing-lag note promises what indexing never delivers R4 Chopper Note Yes
CRF-57 P3 Open exp_chats_test.go:2072 NoSearchableWordsReturnsEmpty pins search:"or" as zero-lexeme, but 'or' is a real lexeme and the assertion passes only because the DB has no chats R4 Hisoka P3, Gon P3, Leorio P3, Meruem P3, Mafuuu Nit, Kite Nit Yes

Contested and acknowledged

CRF-5 (P2, searchQuery.test.ts:96) - No mechanical guard that the backend parser accepts emitted queries

  • Finding: Tests assert only the emitted string shape; nothing mechanically checks that searchquery.Chats accepts the emitted queries. This failure class shipped twice (PR feat(site/src/pages/AgentsPage): wire chat search box to full-text search #27375, then commit 7fc93ab in this PR).
  • Author defense (R2): "Held off per product decision." Emitted queries were verified against the real parser and a live Postgres during development; "this class of contract check is what e2e tests are for." No linked ticket.
  • Status: acknowledged. The panel evaluates whether the e2e rationale holds (does such e2e coverage exist for this query path?).
  • Panel re-raise (R2): The rationale fails on inspection. Five reviewers independently grepped site/e2e/: zero chat coverage of any kind, no ticket to create it. Mafu-san additionally found the current suite already blesses a query the backend rejects (pr_status:"open merged", CRF-26). A deferral without a ticket is a drop; re-raised as P2.

CRF-6 (P3, searchQuery.ts:147) - User-typed search:foo re-wrapped as search:"search:foo"

  • Finding: A typed search:fix token is treated as bare text and folded, so the FTS query ANDs the literal word "search" into the query, skewing results to near-zero matches.
  • Author defense (R2): Retained as a design decision: free text is never parsed for key:value; only the four pill keys (has_unread, archived, pr_status, diff_url) are extracted. A typed search:fix is literal search text by design.
  • Status: contested. The panel judges whether the defense holds against the consequence (users who learn search: from API docs or network traffic get silently skewed results).
  • Panel closure (R2, 7/7 accept): Bisky, Hisoka, Pariston, Mafu-san, Gon, Leorio, Meruem, and Nami all accepted the defense on the same grounds: the rule is uniform (no key:value parsing of free text; title:, pr:, foo: behave identically, pinned by the "never parses free text as structured filters" test), the typed text stays visible in the box, and special-casing search: would reintroduce the per-key parsing seam this PR deleted. Failure mode is bad ranking, recoverable in place, not corruption or a 400.

CRF-18 (Note, searchQuery.ts:17) - Backend filters pr:, repo:, pr_title:, source: swallowed into free text

  • Finding: pr:123 becomes search:"pr:123" instead of the backend's exact PR-number filter; extending the passthrough set collides with the search/pr/pr_title mutual exclusion.
  • Author defense (R2): Accepted as out of scope: those keys have no pills, typed forms are literal search text by design, and adding pills must reckon with the mutual exclusion.
  • Status: acknowledged.
  • Panel closure (R2): accepted under the same uniformity rule as CRF-6. Typed pr:123 searching literal text instead of the exact-PR filter is a capability gap, not corruption, and adding those pills must reckon with the search/pr/pr_title mutual exclusion first.

Round log

Round 6

Panel (Netero first pass: no findings, all fixes verified). 13 reviewers: Bisky (none), Hisoka, Pariston (none), Mafu-san (Notes), Mafuuu, Gon, Leorio, Komugi (none), Nami, Meruem, Ging-go (none), Chopper (none), Kite. CRF-51 closed. All 31 claimed fixes verified by at least two reviewers each. New: 2 P3 (CRF-57 tautological or test case; CRF-56 re-raised as the OR exclusion in hasSearchText), 3 Nit folded into the two findings' comments (OR grouping in searchQuery.test.ts:92, KNOWN_FILTER_KEYS naming, debouncedQueryInput naming). Correction to the record, second instance: the CRF-44 fix used "or" as a zero-lexeme example after R4 had already corrected that premise; the correction did not propagate from the round log to the fix. Reviewed against 19fdc23..1fcef68. (Findings are R6; the R4 tag in the table marks the round the underlying seam was introduced.)

Round 5

BLOCKED. Churn guard: 18 addressed, 1 silent (CRF-51, swagger annotation sentence for zero-lexeme behavior; no code change, no reply). No reviewers spawned. Posted COMMENT naming the blocker. Head: 3132d51. CI green.

Round 4

Panel (Netero first pass: 1 P2, 1 P3, 2 Nit, 2 Note). 13 reviewers: Bisky, Hisoka, Pariston, Mafu-san, Mafuuu, Gon, Leorio, Komugi, Nami, Meruem, Ging-go (no findings), Chopper, Kite. CRF-29 closed (author corrected the record). All R1/R2 fixes verified in code by multiple reviewers each. CRF-24 re-raised (comma-only pr_status pills bypass the sanitize-only commit guard). New: 11 P3 (CRF-36..46), 2 Nit (CRF-50, 51), 5 Note (CRF-52..56). 3 dropped to P4 (comment-bloat class). Correction to the record: R2's CRF-22 canonical example was wrong, a lone "or" tokenizes to the lexeme 'or' under the simple config (Pariston, Meruem, verified live); the class was real via other inputs and is now closed at the root by 026020b. Reviewed against 19fdc23..026020b.

Round 3

BLOCKED. Churn guard: 11 addressed, 1 acknowledged, 1 silent (CRF-29, no author response to the misleading "Fixed." reply label). No reviewers spawned. Posted COMMENT naming the blocker. Head: 86445a0. CI failing (2 checks).

Round 2

Panel (Netero first pass: 1 P2, 1 P3, 1 re-raise, 2 Notes). 11 reviewers: Bisky, Hisoka, Pariston, Mafu-san, Mafuuu, Gon, Leorio, Komugi, Nami, Meruem, Takumi. R1 dispositions: 10 fixes verified, CRF-6 closed (7/7 accept), CRF-18 closed, CRF-5 re-raised P2 (e2e coverage nonexistent, no ticket). New: 3 P2 (CRF-20 debounce identity, CRF-21 operator overclaim, plus the CRF-5 re-raise), 8 P3, 2 Notes. 4 dropped to P4 (Gon comment-bloat class collapsed; mockChat deferral wording). Reviewed against 19fdc23..2ee34e5.

Round 1

Panel (Netero first pass: P3 + Note, below gate). 11 reviewers: Bisky, Hisoka, Pariston, Mafu-san, Mafuuu, Gon, Leorio, Ging-ts (no findings), Komugi, Nami, Meruem. 5 P2, 5 P3, 2 Nit, 2 Note new. 4 Gon comment-bloat findings downgraded to P4 and collapsed into one representative comment. Reviewed against 19fdc23..127bebe.

About deep-review

CRF = Coder Review Finding (P0-P4, Nit, Note)

Reviewer Focus
Bisky tests
Chopper ops/errors
Churn-guard change verification
Ging language modernization
Gon naming
Hisoka edge cases
Killua perf
Kite change integrity
Knov contracts
Knuckle SQL
Komugi flake/determinism
Kurapika security
Law decomposition
Leorio docs
Luffy product
Mafu-san process
Mafuuu contracts
Melody dispatch/pairing
Meruem structural
Nami frontend
Netero mechanical checks
Pariston premise testing
Pen-botter product gaps
Razor verification
Robin duplication
Ryosuke Go arch
Takumi concurrency
Zoro shape

🤖 Managed by Coder Agents.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 127bebe103

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/dialogs/searchQuery.ts Outdated
coder-agents-review[bot]

This comment was marked as outdated.

@DanielleMaywood
DanielleMaywood force-pushed the danielle/codagt-726-chat-search-ui-search-box-wiring branch from 127bebe to 52fa01c Compare August 10, 2026 11:00
…uctured state

Replace the two-pass string parser with pure helpers that build the wire
query directly from pill + free-text state, so free text is never
re-parsed for key:value. Typed recognized filters are extracted into
pills (quote-aware, no early commit on unbalanced quotes, separators
preserved mid-string). Drop the title: special case; title: input is now
literal search text and never triggers the search/title 400.

Also fix three interaction bugs: a Unicode-aware guard replaces an
ASCII-only check so non-ASCII searches work and underscore-only input
does not 400; a single atomic debounce stops a committed filter value
from briefly reappearing as search text; and the empty-state indexing
note only appears when a search token was actually emitted.
@DanielleMaywood
DanielleMaywood force-pushed the danielle/codagt-726-chat-search-ui-search-box-wiring branch from 52fa01c to 2ee34e5 Compare August 10, 2026 11:08
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review model:kimi-k3 thinking:xhigh

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ee34e5ed5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/dialogs/searchQuery.ts Outdated
coder-agents-review[bot]

This comment was marked as outdated.

Address round-2 review on the chat search box:

- Debounce a primitive query snapshot instead of a fresh object, so the
  debounce no longer depends on React Compiler memoization (CRF-20).
- Document that OR and -negation stay live while quoted phrases flatten
  to AND-of-words; the backend tokenizer cannot carry embedded quotes
  (CRF-21).
- Map the backend "no searchable words" 400 to the empty state instead
  of a raw error alert (CRF-22).
- Upsert a typed filter whose key already has a pill (last-write-wins)
  instead of silently discarding it (CRF-23).
- Refuse to commit filter pills whose value sanitizes to empty (CRF-24).
- Append the separator when typed-filter extraction leaves trailing
  text, and only extract when the caret is at the end (CRF-25).
- Emit pr_status values comma-separated, the form the backend accepts
  (CRF-26).
- Pin diff_url first-colon extraction and document the extractTypedFilters
  contract (CRF-27, CRF-28). Remove a single-use alias and stale comments
  (CRF-34).

Add contract coverage (CRF-5): a Go test asserting searchquery.Chats
accepts every query shape the frontend emits, a shape test for the
sidebar list emitter, and a Playwright smoke spec that searches chats
end to end.
@DanielleMaywood DanielleMaywood changed the title feat(site/src/pages/AgentsPage): wire chat search box to full-text search feat: wire chat search box to full-text search Aug 10, 2026
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review model:kimi-k3 thinking:xhigh

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 86445a02b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/dialogs/searchQuery.ts Outdated
Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/dialogs/ChatSearchResults.tsx Outdated
coder-agents-review[bot]

This comment was marked as off-topic.

Two review fixes:

- A search whose value tokenizes to zero lexemes (operator-only input
  like OR, or punctuation) is not an error. listChats now returns 200
  with an empty list instead of a 400, and the frontend drops the
  message-string special case that violated the error-handling
  guideline (match by status, not message text). The no-results state
  is reached naturally from the empty response.
- pr_status splitting no longer produces empty entries from
  comma-plus-space input such as "open, merged"; separators are
  normalized without empty segments.
@DanielleMaywood

This comment was marked as outdated.

@DanielleMaywood

This comment was marked as outdated.

chatgpt-codex-connector[bot]

This comment was marked as outdated.

coder-agents-review[bot]

This comment was marked as outdated.

Address round-3 review:

- Delete the ChatSearchQueryIsEmpty pre-check. A zero-lexeme search
  matches nothing naturally, so the extra DB round trip on every search
  and its querier/dbauthz/dbmock/dbmetrics surface are removed (CRF-36).
- Gate pill creation on the same normalization the emitter uses, so a
  comma-only pr_status value cannot become an active pill that is dropped
  from the query (CRF-24).
- Suppress a stale query error once the search becomes inactive, so
  clearing the input returns to the default view immediately.
- Validate pill values (pr_status enum, boolean flags, diff_url scheme
  and host) so the UI never emits a filter the backend rejects (CRF-38).
- Encode the debounced snapshot as JSON instead of an uncommented
  1/0 prefix, and document why it must stay a primitive (CRF-37).
- Make the caller own the extraction separator; extractTypedFilters no
  longer computes a dead trailing-space hint (CRF-39).
- Test hygiene: real negative guard for the empty-value story, the
  missing unread sidebar shape in the contract table, plural typed-filter
  extraction, an or zero-lexeme backend case, and an honest story name
  (CRF-40, CRF-41, CRF-42, CRF-43, CRF-44).
- Comment cleanup: move the quoting rationale to the wrapping function,
  rename the debounced value, share the known-keys set, and strengthen
  the e2e alert-absence assertion (CRF-46, CRF-53, CRF-54).
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

coder-agents-review[bot]

This comment was marked as outdated.

Append to the search: swagger clause that a value tokenizing to no
searchable words returns an empty list, so API consumers are not
surprised by the 200-with-empty-results behavior (CRF-51).

Copy link
Copy Markdown
Contributor Author

CRF-51 addressed in 1fcef68: the swagger @Param q search: clause (coderd/exp_chats.go:360) now documents that a value tokenizing to no searchable words returns an empty list, and the generated artifacts (apidoc/docs.go, apidoc/swagger.json, docs/reference/api/chats.md) are regenerated. Thanks for catching that it lived in the round-4 body with no thread; replying here so it is no longer silent.

🤖 Coder Agents

@github-actions

Copy link
Copy Markdown
Contributor

Docs preview

Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here.

@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

/coder-agents-review

coder-agents-review[bot]

This comment was marked as outdated.

A lone or/AND/NOT (any casing) is a lexeme under the simple FTS config,
not a zero-lexeme operator. Two earlier fixes were built on the wrong
premise:

- Drop the OR exclusion from hasSearchText so a lone operator word gets
  the indexing-lag note like any other word (CRF-56).
- Seed a control chat containing "or" in the backend test so search:"or"
  is pinned as matching it and search:"!!!" as empty, instead of
  asserting empty on an empty database (CRF-57).

Also rename KNOWN_FILTER_KEYS to CHAT_SEARCH_KNOWN_FILTER_KEYS to match
its siblings, and debouncedQueryInput to debouncedQueryResult.
@DanielleMaywood
DanielleMaywood marked this pull request as ready for review August 11, 2026 09:46
@DanielleMaywood
DanielleMaywood marked this pull request as draft August 11, 2026 09:47
@coderagents

coderagents Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Documentation Check

Updates Needed

  • docs/ai-coder/agents/chat-search-syntax.md - Add a search: row to the Filters table and document its semantics (full-text search across chat titles, PR titles, PR numbers, and message bodies; quote multi-word values; cannot be combined with title, pr_title, or pr). This PR makes search: the default behavior for free text in the search box, but the page still omits it. The intro (line 5) and Notes (line 59) also claim bare/free-text search is rejected with HTTP 400; that framing is now stale. A search: value that tokenizes to no searchable words returns an empty list (200), not a 400, per the pre-check removal in coderd/exp_chats.go. Add an example and a note covering the zero-lexeme case and the Google-style operators (OR, -) the box now passes through.

Note: The base search: filter landed in #27126, which did not update this page. This PR completes the user-facing feature (search box wiring) and changes the zero-lexeme behavior, so the page should be brought in sync here.

The regenerated docs/reference/api/chats.md is auto-generated from the swagger annotations in coderd/exp_chats.go and is already correct; no manual edits needed there.


Automated review via Coder Agents

chatgpt-codex-connector[bot]

This comment was marked as outdated.

- A quote-only search (e.g. a lone ") sanitizes to empty but still emits a
  search token (a single space, since the backend rejects an empty value),
  so it shows no results rather than the unfiltered recent-chats view.
- Typing pr_status:open, merged no longer splits at the space: the
  comma continuation is merged so the pill filters both statuses instead
  of emitting pr_status:open plus a full-text search for merged.
- Drop the unused knownKeys parameter from extractTypedFilters (single
  caller, module constant) and the stripSurroundingQuotes helper.
- Remove frontend boolean and pr_status lowercasing the backend already
  does; keep the comma/whitespace split for pr_status, which the backend
  requires (CRF-26).
- Debounce the built query string directly instead of a JSON-encoded
  snapshot, deriving the indexing-note flag from the current free text.
@DanielleMaywood

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b1846b701a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Let comma-spaced PR statuses finish before committing

When a user selects the PR status pill, types open,, and presses Space to continue with merged, this validator normalizes open, to open and accepts it, so the keydown handler commits the pill before the space is inserted. The following merged becomes free-text search, producing pr_status:open search:"merged" instead of pr_status:open,merged. The fresh evidence after the earlier normalization fix is that the incomplete-pill keydown path still validates and commits the trailing-comma value; allow Space to continue a PR-status list when the raw value ends in a separator.

AGENTS.md reference: site/AGENTS.md:L16-L17

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 450d239. In the incomplete pr_status pill, pressing Space with a value ending in a comma no longer commits the partial value; the space lands so the list can continue, and Enter still commits. The committed value is normalized to the comma-separated form, so a pill typed as open, merged becomes pr_status:open,merged. Added a character-by-character story asserting the pill and the emitted query are pr_status:open,merged and that no pr_status:open search:"merged" request occurs; verified against the real searchquery.Chats that pr_status:open,merged parses to statuses=[open merged].

🤖 Coder Agents

In the incomplete PR-status pill, pressing Space with a value ending in a
comma no longer commits the partial value; the space lands so the user
can continue the list. Enter still commits, and the committed value is
normalized to the comma-separated form the backend accepts.
- Use a Set for boolean filter values instead of a regex.
- Tighten the search-emission comment.
- Keep the diff_url scheme/host pre-check: new URL alone accepts
  "https:///pull/1" as host "pull", so the regex guard is load-bearing.
@DanielleMaywood
DanielleMaywood marked this pull request as ready for review August 12, 2026 08:49
new URL plus the protocol and host check is enough for the common cases
(no scheme, wrong scheme, garbage input). The rare empty-host form
("https:///pull/1") slips through to a clear, field-level backend 400,
which is acceptable feedback rather than a broken state.
@DanielleMaywood
DanielleMaywood merged commit c424a76 into main Aug 12, 2026
31 checks passed
@DanielleMaywood
DanielleMaywood deleted the danielle/codagt-726-chat-search-ui-search-box-wiring branch August 12, 2026 14:04
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 12, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants