Skip to content

feat(site/src): reintroduce chat search cache invalidation - #27892

Merged
DanielleMaywood merged 1 commit into
mainfrom
feat/chat-search-invalidation
Aug 6, 2026
Merged

feat(site/src): reintroduce chat search cache invalidation#27892
DanielleMaywood merged 1 commit into
mainfrom
feat/chat-search-invalidation

Conversation

@DanielleMaywood

Copy link
Copy Markdown
Contributor

Stacked on feat/chat-cache-semantic-ops.

Bug

Chat search results (chatSearch queries) were never invalidated, so the search dialog served stale results after archives, renames, deletions, new chats, message edits, and watch-driven status changes.

Fix

Reintroduces invalidateChatSearches, a prefix invalidation over the module-private chatSearchFamilyKey, and wires it into:

  • chats.ts: archiveChat.onSettled, unarchiveChat.onSettled, updateChatTitle.onSettled, editChatMessage.onSettled, createChat.onSuccess
  • useChatStore.ts: upsertCacheMessages (unconditional; assistant message bodies are indexed too) and replaceCacheMessages
  • AgentsPageLayout.tsx: the deleted and root created watch branches, the merge watch branch (gated by a new exported shouldInvalidateChatSearches helper), the has_unread clearing effect, the onOpen reconnect convergence, and archiveAndDeleteMutation.onSuccess

The merge-branch gate only invalidates for search-affecting event kinds (title_change, status_change, diff_status_change, action_required). summary_change, chat_summary_change, and context_dirty are excluded: stale last_turn_summary subtitles are accepted until reconciliation lands.

Backend constraint

Message bodies only enter full-text search via the dbpurge backfill (search_tsv starts NULL and is populated every 10 minutes). Frontend invalidation fixes removals, ordering, and rendered fields immediately, but a chat that newly matches on message body will not appear until the next backfill. This is a server-side eventual-consistency limit we accept.

Scope decisions (confirmed)

  • No invalidation in createChatMessage.onSuccess: the send path already routes through useChatStore.upsertCacheMessages; adding both would double-invalidate every send.
  • No invalidation for pinChat/unpinChat/reorderPinnedChat (ordering-only, self-heals).
  • ACL mutations out of scope.
  • No coalescing/debouncing; that belongs to a later reconciler PR.

Tests

  • Prefix invalidation: multiple distinct q params invalidated, bystanders (list, by-workspace, entity, messages, cost tree) untouched.
  • Mutation wiring: settlement of archiveChat, unarchiveChat, updateChatTitle, editChatMessage, and createChat invalidates a seeded search key; createChatMessage asserted NOT to.
  • shouldInvalidateChatSearches unit-tested over all ChatWatchEventKind values.

PR generated by Coder Agents.

@DanielleMaywood
DanielleMaywood force-pushed the feat/chat-search-invalidation branch from 510ce6a to 683335d Compare August 5, 2026 20:12
@DanielleMaywood
DanielleMaywood force-pushed the feat/chat-search-invalidation branch from 683335d to d420ea1 Compare August 5, 2026 20:23
Base automatically changed from feat/chat-cache-semantic-ops to main August 6, 2026 07:29
@DanielleMaywood
DanielleMaywood force-pushed the feat/chat-search-invalidation branch from d420ea1 to fe41cbe Compare August 6, 2026 08:22
@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: fe41cbe176

ℹ️ 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/AgentsPageLayout.tsx
@DanielleMaywood
DanielleMaywood force-pushed the feat/chat-search-invalidation branch from fe41cbe to dbd41cb Compare August 6, 2026 08:42
@DanielleMaywood
DanielleMaywood marked this pull request as ready for review August 6, 2026 09:01
@DanielleMaywood
DanielleMaywood merged commit 632eecc into main Aug 6, 2026
31 checks passed
@DanielleMaywood
DanielleMaywood deleted the feat/chat-search-invalidation branch August 6, 2026 09:25
DanielleMaywood added a commit that referenced this pull request Aug 6, 2026
… watch paths (#27901)

Implements Phase 2 item 5 of the chats query architecture: by-workspace
cache reconciliation. Stacked on #27892; base branch
`feat/chat-search-invalidation`.

## Problem

`chatsByWorkspace` mappings (flat `Record<workspaceId, chatId>`, IDs
only, archived chats filtered server-side) go stale on
archive/unarchive, workspace binding changes, and watch events (FINDINGS
2.3). Because the client cannot re-derive archived state from the cached
map, the only correct repair for a stale mapping is synchronous removal
plus family invalidation.

## Fix

- New `removeChatFromChatsByWorkspace(queryClient, chatId)` in
`site/src/api/queries/chats.ts`: value-match removal across the
by-workspace family, reference-preserving when nothing is removed
(mirrors the `patchChatMessages` no-op pattern).
- `archiveChat.onSuccess` and the AgentsPageLayout archive-and-delete
`onSuccess` synchronously remove the mapping; the existing
`onSettled`/explicit invalidations then reconverge.
- Watch handler: `deleted` branch removes then invalidates
(remove-before-invalidate ordering); `created` root branch invalidates;
merge path invalidates behind a new `shouldInvalidateChatsByWorkspace`
predicate (`status_change`, `action_required` only; `created`/`deleted`
have their own branches, title/summary/diff/context events do not move
`updated_at` ordering); `onOpen` reconnect invalidates for convergence.
- `useChatToolInvalidations`: workspace-binding tool completion
(`create_workspace`) also invalidates by-workspace; this is the only
reconciliation path on the embed route.

## Confirmed exclusions

- No optimistic patch in `updateChatWorkspace.onMutate`; the awaited
`onSettled` invalidation already converges.
- No cancellation guard for the by-workspace family (Phase 2 item 9
territory).
- No `createChat` change (already invalidates); no ACL or pin changes.

## Known constraints

- Cascade archives remove only the event's own chat ID from the mapping;
per-family-member `deleted` events plus family invalidation repair the
rest.
- The REST `patchChat` workspace-rebind branch publishes no watch event
server-side, so cross-session manual rebinds converge only via the
acting session's `onSettled` or a WorkspacesPage remount; not fixable
client-side.

## Testing

- `chats.test.ts`: removal scoping and reference-preservation tests;
`it.each` wiring tests (archiveChat/unarchiveChat/updateChatWorkspace
onSettled, createChat onSuccess invalidate by-workspace);
synchronous-removal assertion for `archiveChat.onSuccess`; negative
assertions for `updateChatTitle` and `createChatMessage`.
- `AgentsPageLayout.test.ts`: exhaustive `ChatWatchEventKind` table for
`shouldInvalidateChatsByWorkspace`.
- `useChatToolInvalidations.test.tsx`: create_workspace regression test
extended with a seeded by-workspace bystander.

PR generated by Coder Agents.
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 6, 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