perf(sessions): persist conversation build caches across task re-opens - #3976
perf(sessions): persist conversation build caches across task re-opens#3976arnohillen wants to merge 5 commits into
Conversation
|
Merging to
After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here |
|
React Doctor found no issues in the changed files. 🎉 Reviewed by React Doctor for commit |
Re-opening a task unmounts and remounts the whole detail view, so the incremental conversation builder and thread grouper (both component-scoped) were rebuilt from scratch on every click, re-parsing the full transcript on the main thread. For large sessions this blocks for seconds. Keep both in a module-level LRU cache keyed per call site and task, so a re-open reuses the already-built items. Entries are dropped when the residency system evicts a session's events, so memory reclaim still works. Generated-By: PostHog Code Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
|
35dacaf to
c339368
Compare
The cap counted scope+task entries in one map, so the three opted-in scopes consumed three slots per task and only ~2 tasks stayed fully cached instead of 8. Group entries per scope and cap within each scope. Generated-By: PostHog Code Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
|
Good catch on the LRU counting scope+task pairs. Fixed in 35a5be0: entries are now grouped per scope with the cap applied inside each scope, so the three conversation scopes no longer shrink the per-task capacity. Added a test asserting a task in one scope survives filling another scope to the cap. |
lru-cache v11 is already a workspace dependency (harness), so this swaps the hand-rolled Map re-insertion LRU for the battle-tested implementation with identical semantics: get() refreshes recency, max evicts per scope. Generated-By: PostHog Code Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
Same swap as the conversation caches in #3976: lru-cache v11 is already a workspace dependency, so the hand-rolled capped Map goes away. Generated-By: PostHog Code Task-Id: 5d24ea17-aec0-4334-884e-c2639867e260
- Sweep entries by the session run they were built from, latched only once that run's events are store-resident: a re-run's residency no longer keeps a stale entry alive, and sessions with no resident events no longer cause sweep churn - Pin the cache entry for the mounted lifetime so LRU eviction never forces a still-mounted view (e.g. a 3x3 command-center grid, one over the cap) onto a fresh builder every render - Stop persisting ChatThreadFooter's duplicate full parse: AcpChatThread now passes footerState and usage down exactly like ChatThread, halving per-task derived retention and removing the chat-thread-footer scope - Merge the two cache registries into one entry per scope+task, subscribe at module scope, skip sweeps when the sessions slice is unchanged, and add a 30 minute TTL so sessionless (archive) entries reclaim on their own - Type the scope as a closed union and accept an optional taskId in the persist key so call sites drop the ternaries Generated-By: PostHog Code Task-Id: 47cdc119-1c24-42ec-ac36-74c43cdd7f4e
Not run in CI (test globs only match *.test.*). Numbers land in the PR description; rerun with pnpm vitest bench in packages/ui. Generated-By: PostHog Code Task-Id: 47cdc119-1c24-42ec-ac36-74c43cdd7f4e
Problem
Clicking a task in the sidebar takes seconds before the transcript renders, even when the session data is already warm in the store. The task detail route fully unmounts on navigation, and the incremental conversation builder (
useConversationItems) and thread grouper both live in component-scoped refs, so every re-open falls back to a full O(n) re-parse of the transcript on the main thread.yieldToPaintalready documents the mount blocking "for hundreds of ms"; with multi-MB transcripts it is seconds.Changes
conversationDerivedCachemodule: a small LRU cache (8 tasks per scope, backed bylru-cache, already a workspace dependency via harness) keyed per call site + task that keeps the incremental builder state and thread grouper alive across mounts.useConversationItemsaccepts an optional persist key; without one, behavior is unchanged (per-component ref).ConversationView,AcpChatThread, andChatThreadFooteropt in with distinct scopes (distinct scopes matter: the thread and its footer feed different inputs for the same task, and sharing one builder would make them invalidate each other every render).Warm re-opens (session still in the store) now reuse already-built items: idle sessions return the memoized result identity, and streaming sessions take the append-only fast path because the store keeps appending while the view is unmounted and immer preserves element identity.
How did you test this?
evictEvents/removeSession, and the no-session exemption (guards rebuild churn on archive surfaces).@posthog/uivitest suite: 281 files, 2386 tests passing.pnpm typecheckand biome via lint-staged.Automatic notifications
Created with PostHog Code