Skip to content

Memoize derived thread reads#1908

Merged
juliusmarminge merged 4 commits intomainfrom
feature/memoize-thread-derivation
Apr 11, 2026
Merged

Memoize derived thread reads#1908
juliusmarminge merged 4 commits intomainfrom
feature/memoize-thread-derivation

Conversation

@juliusmarminge
Copy link
Copy Markdown
Member

@juliusmarminge juliusmarminge commented Apr 11, 2026

Summary

  • Memoize derived thread reads so repeated selectors can reuse stable thread objects when underlying state is unchanged.
  • Extract shared thread derivation logic into threadDerivation.ts to reduce duplication between store selectors and store updates.
  • Add coverage for selector stability, cache invalidation, and thread existence checks.
  • Simplify the chat thread route to use the new existence selector and avoid an extra effect for diff-open tracking.

Testing

  • Added apps/web/src/store.test.ts coverage for memoized thread derivation and existence checks.
  • Not run: bun fmt
  • Not run: bun lint
  • Not run: bun typecheck
  • Not run: bun run test

Note

Medium Risk
Introduces new WeakMap-based memoization for derived Thread objects and refactors selectors/store updates to use it, which could subtly affect referential equality and UI update behavior. Route-level diff panel mounting logic also changes state tracking per-thread, so regressions would mainly show up as stale/incorrect renders rather than data corruption.

Overview
Memoizes derived thread reads by centralizing thread derivation in getThreadFromEnvironmentState (new threadDerivation.ts) and caching derived Thread objects/collected arrays to keep references stable when underlying environment state is unchanged.

Updates store.ts and storeSelectors.ts to use the shared derivation helper, adds a lightweight selectThreadExistsByRef for existence checks without materializing threads, and adds tests in store.test.ts covering stability, invalidation, and existence.

Tweaks the chat thread route/diff UX: tracks “diff panel has opened” per thread key, passes onDiffPanelOpen into ChatView, and triggers it when opening/toggling diffs so diff content can stay mounted after first open.

Reviewed by Cursor Bugbot for commit 081d29b. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Memoize derived thread reads to avoid redundant recomputation

  • Introduces getThreadFromEnvironmentState in threadDerivation.ts, which uses a WeakMap keyed on the thread shell to cache the fully composed Thread object, returning the same reference when inputs are unchanged.
  • Adds a memoized collectByIds helper that caches derived arrays for messages, activities, proposed plans, and turn diff summaries using WeakMaps.
  • Refactors createScopedThreadSelector in storeSelectors.ts to track EnvironmentState and threadId instead of individual per-thread slices, delegating derivation to getThreadFromEnvironmentState.
  • Adds selectThreadExistsByRef selector in store.ts to check thread existence without materializing the full derived thread.
  • Updates the chat thread route to track per-thread diff panel open state via an onDiffPanelOpen callback on ChatView, avoiding unnecessary re-renders tied to diffOpen.

Macroscope summarized 081d29b.

- Extract shared thread derivation
- Add existence selector for route checks
- Cover memoization behavior with tests
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 11, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a7536464-454d-4bc4-b022-22f2df1c1d43

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/memoize-thread-derivation

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:L 100-499 changed lines (additions + deletions). labels Apr 11, 2026
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Removed effect breaks diff content persistence after close
    • Restored a useEffect that watches diffOpen and sets hasOpenedDiff(true) whenever the diff becomes open, ensuring the DiffPanel stays mounted after close regardless of how the diff was opened.

Create PR

Or push these changes by commenting:

@cursor push f0ed1ba77a
Preview (f0ed1ba77a)
diff --git a/apps/web/src/routes/_chat.$environmentId.$threadId.tsx b/apps/web/src/routes/_chat.$environmentId.$threadId.tsx
--- a/apps/web/src/routes/_chat.$environmentId.$threadId.tsx
+++ b/apps/web/src/routes/_chat.$environmentId.$threadId.tsx
@@ -204,11 +204,15 @@
       search: { diff: undefined },
     });
   }, [navigate, threadRef]);
+  useEffect(() => {
+    if (diffOpen) {
+      setHasOpenedDiff(true);
+    }
+  }, [diffOpen]);
   const openDiff = useCallback(() => {
     if (!threadRef) {
       return;
     }
-    setHasOpenedDiff(true);
     void navigate({
       to: "/$environmentId/$threadId",
       params: buildThreadRouteParams(threadRef),

You can send follow-ups to the cloud agent here.

Comment thread apps/web/src/routes/_chat.$environmentId.$threadId.tsx Outdated
@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 11, 2026

Approvability

Verdict: Approved

This is a performance optimization refactor that centralizes thread derivation logic into a new module with WeakMap-based memoization. The changes are well-tested, functionally equivalent to existing behavior, and don't introduce new features or runtime behavior changes.

You can customize Macroscope's approvability policy. Learn more.

- Track diff-open state per thread key
- Notify route when chat opens the diff panel
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Four exported functions are never imported externally
    • Removed the export keyword from selectThreadMessages, selectThreadActivities, selectThreadProposedPlans, and selectThreadTurnDiffSummaries since they are only used internally within threadDerivation.ts.

Create PR

Or push these changes by commenting:

@cursor push 9176389c68
Preview (9176389c68)
diff --git a/apps/web/src/threadDerivation.ts b/apps/web/src/threadDerivation.ts
--- a/apps/web/src/threadDerivation.ts
+++ b/apps/web/src/threadDerivation.ts
@@ -60,10 +60,7 @@
   return nextValues;
 }
 
-export function selectThreadMessages(
-  state: EnvironmentState,
-  threadId: ThreadId,
-): Thread["messages"] {
+function selectThreadMessages(state: EnvironmentState, threadId: ThreadId): Thread["messages"] {
   return collectByIds(
     state.messageIdsByThreadId[threadId],
     state.messageByThreadId[threadId] ?? EMPTY_MESSAGE_MAP,
@@ -71,10 +68,7 @@
   );
 }
 
-export function selectThreadActivities(
-  state: EnvironmentState,
-  threadId: ThreadId,
-): Thread["activities"] {
+function selectThreadActivities(state: EnvironmentState, threadId: ThreadId): Thread["activities"] {
   return collectByIds(
     state.activityIdsByThreadId[threadId],
     state.activityByThreadId[threadId] ?? EMPTY_ACTIVITY_MAP,
@@ -82,7 +76,7 @@
   );
 }
 
-export function selectThreadProposedPlans(
+function selectThreadProposedPlans(
   state: EnvironmentState,
   threadId: ThreadId,
 ): Thread["proposedPlans"] {
@@ -93,7 +87,7 @@
   );
 }
 
-export function selectThreadTurnDiffSummaries(
+function selectThreadTurnDiffSummaries(
   state: EnvironmentState,
   threadId: ThreadId,
 ): Thread["turnDiffSummaries"] {

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 594dd8f. Configure here.

Comment thread apps/web/src/threadDerivation.ts
@juliusmarminge
Copy link
Copy Markdown
Member Author

@cursor push 9176389

@juliusmarminge juliusmarminge merged commit b80e847 into main Apr 11, 2026
6 of 7 checks passed
@juliusmarminge juliusmarminge deleted the feature/memoize-thread-derivation branch April 11, 2026 02:06
juliusmarminge added a commit that referenced this pull request Apr 13, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
znoraka pushed a commit to znoraka/t3code that referenced this pull request Apr 17, 2026
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants