fix(agents): use correct SWRInfinite cache key and add optimistic update for session editing#13475
Conversation
…ate for session editing The global mutate(listKey) in useUpdateSession did not match useSWRInfinite's internal cache key format, causing stale UI after session edits. Use unstable_serialize from swr/infinite to generate the correct cache key, and implement optimistic updates for immediate UI feedback with rollback on failure. Fixes #13474 Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> Signed-off-by: icarus <[email protected]>
Replace SessionName styled-component with Tailwind `truncate` utility. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> Signed-off-by: icarus <[email protected]>
Move the session page size constant from useSessions.ts to AgentApiClient module as it is an API-level concern shared by both useSessions and useUpdateSession. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> Signed-off-by: icarus <[email protected]>
There was a problem hiding this comment.
Note
This issue/comment/review was translated by Claude.
LGTM! High-quality bug fix:
- Correctly use
unstable_serializeto resolve SWRInfinite cache key matching issue - Optimistic update + failure rollback mechanism is complete
- Includes Tailwind code cleanup
- No breaking changes
Thank you for your contribution!
Original Content
LGTM! 高质量的 bug 修复:
- 正确使用
unstable_serialize解决 SWRInfinite 缓存键匹配问题 - 乐观更新 + 失败回滚机制完善
- 附带 Tailwind 代码清理
- 无破坏性变更
感谢贡献!
| const sessionId = form.id | ||
| const itemKey = paths.withId(sessionId) | ||
| const infKey = unstable_serialize(() => [listKey, 0, DEFAULT_SESSION_PAGE_SIZE]) | ||
|
|
There was a problem hiding this comment.
Concern: unstable_serialize(() => [listKey, 0, DEFAULT_SESSION_PAGE_SIZE]) hardcodes the default page size. If a caller of useSessions passes a custom pageSize, the serialized key here won't match the SWRInfinite cache key, causing the optimistic update to silently miss that cache entry.
Consider either:
- Accept
pageSizeas a parameter inuseUpdateSession, or - Extract the
getKeyfunction fromuseSessionsas a shared utility so both hooks use the same key generation logic.
This would make the coupling between the two hooks explicit and prevent silent cache misses.
| import { useAgentClient } from './useAgentClient' | ||
|
|
||
| type InfiniteData = ListAgentSessionsResponse[] | ||
|
|
||
| const mutateInfiniteList = ( |
There was a problem hiding this comment.
Nit: mutateInfiniteList is a clean extraction. Consider adding a brief JSDoc noting that the infKey must be generated via unstable_serialize from swr/infinite — this would help future contributors understand the coupling.
What this PR does
Before this PR:
Editing a session (e.g., renaming) in the Agent sidebar does not immediately reflect in the UI. The global
mutate(listKey, ...)inuseUpdateSessionfails to matchuseSWRInfinite's internal cache key format, so the list cache is never actually updated. The change only appears after SWR's automatic revalidation (~2-3 seconds).After this PR:
truncateutility instead of a styled-component with-webkit-line-clamp. Also cleaned up Tailwind v4 syntax inshared.tsx(text-[var(--color-text)]→text-(--color-text),p-[16px]→p-4).DEFAULT_SESSION_PAGE_SIZEmoved toapi/agent.tsas a shared API-level constant.Fixes #13474
Why we need it and why it was done in this way
The following tradeoffs were made:
unstable_serializefromswr/infiniteto generate the correct SWRInfinite cache key. This is the official SWR approach for mutatinguseSWRInfinitedata from outside the hook.DEFAULT_SESSION_PAGE_SIZElives inapi/agent.tsbecause page size is an API-level concern shared by bothuseSessionsanduseUpdateSession.The following alternatives were considered:
useSessionshook — rejected becauseuseUpdateSessionis used independently in multiple places (e.g.,SessionSettingsPopup).mutate(key => key.includes(...))) — rejected as it relies on SWR internals and is fragile.useSessions.ts— rejected to avoid a hook module exporting non-hook values.Breaking changes
None.
Special notes for your reviewer
mutateInfiniteListhelper is extracted as a module-level function to avoid recreation on each render.shared.tsxis a drive-by fix.Checklist
/gh-pr-review,gh pr diff, or GitHub UI) before requesting review from othersRelease note