Skip to content

Threads 10: Store ordered thread snapshots in ChatJS - #240

Closed
FranciscoMoretti wants to merge 4 commits into
codex/threads-v2-08-external-statefrom
codex/threads-v2-07-store-tree-snapshot
Closed

Threads 10: Store ordered thread snapshots in ChatJS#240
FranciscoMoretti wants to merge 4 commits into
codex/threads-v2-08-external-statefrom
codex/threads-v2-07-store-tree-snapshot

Conversation

@FranciscoMoretti

@FranciscoMoretti FranciscoMoretti commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Summary

  • Refactors the ChatJS store to carry the package tree snapshot.
  • Derives visible messages, siblings, and tree indexes from one canonical structure.
  • Preserves existing store consumers ahead of adopting useThread.

Behavior

Refactor only. No intended user-visible behavior change.

Verification

  • Chat store unit tests pass at the stack tip.

Review focus

Snapshot synchronization and preservation of current store semantics.


Summary by cubic

Stores a canonical MessageTreeSnapshot from @chatjs/thread in the ChatJS store and derives navigation, sibling order, and indexes from ordered snapshots. Adds ordered snapshot merging and visible-path hydration, and finalizes assistant continuation, native regeneration, and restore-safe tool/approval ownership, with no user-visible changes.

  • Refactors

    • Add treeSnapshot, treeSnapshotSignature, and setTreeSnapshot; merge incoming snapshots with existing state; rebuild the visible path from cursorId (empty when null); no-op when unchanged.
    • Build snapshots from messages; derive childrenMap, parent links, and sibling order (by parallelIndex, then time); recover invalid/cyclic topology as extra roots; preserve paths of any depth; hydrate an empty visible path from server trees.
    • Merge/update rules: preserve hidden branches and optimistic/local nodes; keep metadata when updates omit it; infer assistant fallback metadata from the parent; replace selected placeholders with completed server content (clears activeStreamId); dedupe root user siblings.
    • Streaming: while streaming/submitted, update only the snapshot/indexes; setMessagesWithEpoch/setAllMessages/addMessageToTree rebuild the snapshot and indexes, reset selector caches and _throttledMessages, update _messageIndex.
    • Navigation uses snapshot-derived indexes for switchToSibling/switchToMessage; getParallelGroupInfo is available before any assistant exists.
  • New Features

    • Assistant continuation: sendMessage() on a selected assistant continues that node; passing an explicit assistant message streams into the same message ID; branching still requires a new input.
    • Regeneration: regenerate({ messageId }) uses AI SDK’s regenerate-message trigger, creates a sibling replacement (including root assistants), and only auto-follows when the cursor still targets the message; rejects assistant-with-assistant parent until an SDK fix.
    • Restore and resume: reconstruct tool/approval ownership after restoring a tree; resume restored assistants (root or with assistant parents) without changing topology.

Written for commit edf3932. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Improved conversation thread and branch navigation, including more reliable sibling ordering.
    • Preserved message metadata when updates are incomplete or omitted.
    • Replaced placeholder assistant messages with completed server responses more consistently.
    • Improved handling of tree snapshots and message visibility during streaming and editing.
  • Bug Fixes

    • Fixed branch navigation and synchronization issues that could cause messages or metadata to appear incorrectly.

Stack

  1. Threads 0: Define the useThread target architecture #233
  2. Threads 22: Adopt AI SDK response identity lifecycle #258
  3. Threads 1: Define useThread package contracts #234
  4. Threads 2: Add the canonical message tree #235
  5. Threads 3: Adapt isolated AI SDK runs #236
  6. Threads 4: Orchestrate concurrent tree runs #237
  7. Threads 5: Prove AI SDK behavioral parity #238
  8. Threads 6: Expose the useChat-compatible useThread hook #239
  9. Threads 7: Rename the thread controller #261
  10. Threads 8: Add externally owned thread state #262
  11. Threads 10: Store ordered thread snapshots in ChatJS #240 👈 current
  12. Threads 11: Mount useThread in ChatJS #241
  13. Threads 12: Add branch navigation and retry #242
  14. Threads 13: Isolate branch stream lifecycles #243
  15. Threads 14: Stream follow-up parallel responses #244
  16. Threads 15: Add cancellable request gates #263
  17. Threads 16: Gate first-message parallel runs #245
  18. Threads 17: Stop newly provisioned thread runs #246
  19. Threads 18: Show parallel response lifecycle states #247
  20. Threads 19: Publish installable thread sources #248
  21. Threads 20: Add the thread playground model #249
  22. Threads 21: Add the interactive thread playground #250
  23. Threads 22: Publish the threads product page #251
  24. Threads 23: Add the value-first package guide #252
  25. Threads 24: Document ChatJS threaded behavior #253
  26. Threads 25: Prepare the thread package release #254

@cursor

cursor Bot commented Jul 16, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@sourcery-ai sourcery-ai 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.

Sorry @FranciscoMoretti, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
chat-js-docs Ready Ready Preview Aug 2, 2026 7:02am
chat-js-site Ready Ready Preview Aug 2, 2026 7:02am
sparka Error Error Aug 2, 2026 7:02am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The thread store now uses MessageTreeSnapshot for tree indexing, visible-thread derivation, merging, metadata handling, and navigation. Tests cover snapshot behavior, optimistic branches, streaming replacements, metadata preservation, and editing. The chat app adds the thread package dependency.

Changes

Thread snapshot refactor

Layer / File(s) Summary
Snapshot model and tree indexing
apps/chat/lib/stores/with-threads.ts, apps/chat/package.json
Adds snapshot state, deterministic tree construction, parent/child indexes, traversal helpers, signatures, metadata merging, and the @chatjs/thread dependency.
Snapshot-aware store updates
apps/chat/lib/stores/with-threads.ts
Integrates snapshots into initialization, message updates, streaming state, tree merging, metadata preservation, sibling lookup, and visible-thread state.
Snapshot navigation and behavioral tests
apps/chat/lib/stores/with-threads.ts, apps/chat/lib/stores/with-threads.test.ts
Uses snapshot traversal for navigation and tests metadata handling, optimistic branches, streaming replacement, fallback metadata, and edit behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: storing ordered thread snapshots in the ChatJS store.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/threads-v2-07-store-tree-snapshot

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 71d3dfc64a

ℹ️ 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 apps/chat/lib/stores/with-threads.ts Outdated
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-06-use-thread branch from 03bcd2a to 61f6927 Compare July 16, 2026 18:25
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-07-store-tree-snapshot branch from 71d3dfc to 5a30d6f Compare July 16, 2026 18:25

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/chat/lib/stores/with-threads.ts Outdated
Comment thread apps/chat/lib/stores/with-threads.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR refactors the ChatJS store to carry a canonical MessageTreeSnapshot from @chatjs/thread, replacing the previous thread-utils helpers with inline snapshot-based implementations. Sibling navigation, parallel-group queries, and visible-thread building are all derived from the snapshot, and a new setTreeSnapshot action lets the headless ThreadChat package drive the store directly.

  • New setTreeSnapshot: merges external snapshots with allMessages, applies fallback metadata to shell assistant messages, recomputes the visible thread from the merged snapshot's cursor, and updates treeSnapshot/childrenMap/messages in one set call.
  • setAllMessages two-set pattern: when not streaming, originalSetMessages fires a first Zustand set updating only messages, followed immediately by a second set updating allMessages, childrenMap, treeSnapshot, and _throttledMessages — creating a brief inconsistent-state window for subscribers that read multiple fields.
  • Snapshot index recomputed recursively: findLeafDfsToRightFromSnapshot calls getSnapshotIndexes(snapshot) at each recursion level, making leaf lookup O(n \u00d7 depth) instead of O(n).

Confidence Score: 3/5

The refactor is behaviorally correct in the happy path but setAllMessages fires two separate Zustand set calls in the non-streaming path, leaving a window where messages has advanced while allMessages, childrenMap, and _throttledMessages still reflect the old tree.

The double-set in setAllMessages is the clearest concrete defect introduced: any subscriber combining messages with childrenMap can render with a new message list but stale sibling ordering until the second set lands. Several previously flagged gaps compound the risk in this same file, and the test suite does not exercise torn-state or streaming-interaction edge cases.

Files Needing Attention: apps/chat/lib/stores/with-threads.ts — specifically the setAllMessages non-streaming path and setTreeSnapshot.

Important Files Changed

Filename Overview
apps/chat/lib/stores/with-threads.ts Core refactor: replaces thread-utils imports with inline snapshot-based helpers, adds treeSnapshot/treeSnapshotSignature state, and introduces setTreeSnapshot for headless package integration. Two issues found: setAllMessages fires two separate Zustand set calls creating a brief inconsistent-state window, and findLeafDfsToRightFromSnapshot rebuilds the full snapshot index on every recursive call.
apps/chat/lib/stores/with-threads.test.ts Adds six new test cases covering parallel groups, branch preservation, optimistic node retention, metadata merging, placeholder replacement, fallback metadata, and duplicate-sibling prevention.
apps/chat/package.json Adds @chatjs/thread as a workspace dependency; no version pinning concerns for a monorepo workspace reference.
bun.lock Lock file updated to reflect the new @chatjs/thread workspace package entry; no external registry additions.

Reviews (27): Last reviewed commit: "refactor(chat): derive indexes from orde..." | Re-trigger Greptile

@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-06-use-thread branch from 579d626 to 1212bbc Compare July 21, 2026 06:51
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-07-store-tree-snapshot branch from fcf9e9d to 3fc1ed6 Compare July 21, 2026 06:51
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-06-use-thread branch from 1212bbc to 8668264 Compare July 21, 2026 06:56
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-07-store-tree-snapshot branch 2 times, most recently from bb2e6db to f9d5553 Compare July 21, 2026 07:04
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-06-use-thread branch from 8668264 to 7078da9 Compare July 21, 2026 07:04
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-06-use-thread branch from 7078da9 to 7f16e34 Compare July 21, 2026 07:27
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-07-store-tree-snapshot branch from f9d5553 to 68fb205 Compare July 21, 2026 07:27
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-06-use-thread branch from 7f16e34 to c12565e Compare July 23, 2026 06:47
@FranciscoMoretti
FranciscoMoretti force-pushed the codex/threads-v2-07-store-tree-snapshot branch from 68fb205 to 41c5189 Compare July 23, 2026 06:47
Comment thread apps/chat/lib/stores/with-threads.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (9)
apps/chat/lib/stores/with-threads.ts (7)

504-518: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated metadata-preservation logic; reuse mergeMessageIntoMap.

Lines 504-518 replicate the branch in mergeMessageIntoMap (lines 263-281) verbatim. Extract the shared "keep existing metadata when the update omits it" rule into one helper so the two paths can't drift.

♻️ Sketch
function withPreservedMetadata<UM extends UIMessage>(
  existing: UM | undefined,
  message: UM
): UM {
  const existingMetadata = (existing as (UM & MessageNode) | undefined)?.metadata;
  if (
    existing &&
    (message as UM & MessageNode).metadata === undefined &&
    existingMetadata !== undefined
  ) {
    return { ...message, metadata: { ...existingMetadata } } as UM;
  }
  return message;
}

mergeMessageIntoMap becomes merged.set(message.id, withPreservedMetadata(merged.get(message.id), message)), and addMessageToTree uses next[idx] = withPreservedMetadata(existing, message).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 504 - 518, Extract the
duplicated metadata-preservation branch into a shared helper such as
withPreservedMetadata near mergeMessageIntoMap. Update mergeMessageIntoMap and
addMessageToTree to call this helper, preserving existing metadata only when the
incoming message omits it and otherwise returning the incoming message
unchanged.

362-366: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Snapshot is built three times during initialization.

buildTreeSnapshotFromMessages(base.messages) runs at line 362, again inside getSnapshotSignature at line 363-365, and a third time via rebuildMap's default parameter at line 366. Build once and reuse.

♻️ Proposed refactor
+    const initialSnapshot = buildTreeSnapshotFromMessages(base.messages);
     return {
       ...base,
       threadEpoch: 0,
       threadInitialMessages: base.messages,
       allMessages: base.messages,
-      treeSnapshot: buildTreeSnapshotFromMessages(base.messages),
-      treeSnapshotSignature: getSnapshotSignature(
-        buildTreeSnapshotFromMessages(base.messages)
-      ),
-      childrenMap: rebuildMap(base.messages),
+      treeSnapshot: initialSnapshot,
+      treeSnapshotSignature: getSnapshotSignature(initialSnapshot),
+      childrenMap: rebuildMap(base.messages, initialSnapshot),

Note that initialSnapshot must be declared before the returned object literal to avoid a TDZ access.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 362 - 366, In the
initialization flow surrounding the returned object, compute
buildTreeSnapshotFromMessages(base.messages) once in an initialSnapshot variable
declared before the object literal, then reuse it for treeSnapshot and
getSnapshotSignature. Update rebuildMap to receive the same snapshot explicitly
instead of triggering its default snapshot construction.

248-252: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Full JSON.stringify of the tree as a change signature is expensive and order-sensitive.

This serializes every message (all parts and metadata) and runs on every store mutation — setMessagesWithEpoch, setTreeSnapshot, setAllMessages, addMessageToTree, setMessages — i.e. per streaming chunk. It's also key-order dependent, so two structurally identical snapshots whose message objects were built with different property insertion order produce different signatures, defeating the dedupe at line 420. A compact derived signature (e.g. cursorId plus id:parentId pairs and a per-message revision marker) would be cheaper and stable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 248 - 252, Replace the
full JSON.stringify implementation in getSnapshotSignature with a compact,
stable signature derived from cursorId and each message’s id, parentId, and
revision marker. Avoid serializing message parts or metadata, and ensure the
derived ordering is deterministic so structurally identical snapshots
deduplicate consistently at the existing call sites.

113-127: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid re-allocating the sibling array on every insert.

Lines 119-122 rebuild each children array per message (O(k²) copies per parent bucket). buildTreeSnapshotFromMessages runs on nearly every store mutation, including per streaming update, so push into the existing array instead.

♻️ Proposed refactor
   for (const message of messages) {
     const metadataParentId = getMetadataParentId(message);
     const parentId =
       metadataParentId && messagesById.has(metadataParentId)
         ? metadataParentId
         : null;
-    childrenByParentId.set(parentId, [
-      ...(childrenByParentId.get(parentId) ?? []),
-      message,
-    ]);
+    const siblings = childrenByParentId.get(parentId);
+    if (siblings) {
+      siblings.push(message);
+    } else {
+      childrenByParentId.set(parentId, [message]);
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 113 - 127, Update the
message-grouping loop in buildTreeSnapshotFromMessages to retrieve or initialize
each parent’s sibling array once, then append the current message with push
instead of creating a copied array on every insert. Preserve the existing
parentId assignment and subsequent compareSiblingMessages sorting.

332-353: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

parentById defaulting to {} makes fallback-metadata derivation a silent no-op.

setAllMessages (line 451) calls this with three arguments, so addFallbackMetadataToMessages resolves every parentId to null and never fills anything; only setTreeSnapshot passes the map. If that asymmetry is intentional it deserves a comment; otherwise make the parameter required (or derive it from the merged messages) so the behavior can't be lost by omission.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 332 - 353, Make the
parentById input to mergeTreeMessages required instead of defaulting to an empty
object, and update setAllMessages to provide the appropriate parent map (or
derive it from the merged messages) before addFallbackMetadataToMessages runs.
Preserve setTreeSnapshot’s existing map flow so fallback metadata is populated
consistently and cannot silently no-op when the argument is omitted.

156-175: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

getSnapshotIndexes is rebuilt on every call, including per recursion level.

It walks all snapshot nodes and (line 166) copies each child array per insert. Callers make this quadratic on hot paths:

  • findLeafDfsToRightFromSnapshot (line 235) rebuilds the full index at every recursion depth.
  • getMessageSiblingInfo (line 545) and getParallelGroupInfo (line 567) rebuild it per message lookup, and these run per rendered message.

Consider computing the indexes once per snapshot — e.g. cache keyed on treeSnapshotSignature, or pass the indexes into the traversal helpers instead of the snapshot.

♻️ Minimal fix for the per-insert copy plus a snapshot-keyed cache
-  for (const { message, parentId } of snapshot.nodes) {
-    const key = parentKey(parentId);
-    childrenByParentId[key] = [...(childrenByParentId[key] ?? []), message.id];
+  for (const { message, parentId } of snapshot.nodes) {
+    const key = parentKey(parentId);
+    (childrenByParentId[key] ??= []).push(message.id);
     messagesById[message.id] = message;
     parentById[message.id] = parentId;
     if (parentId === null) {
       rootIds.push(message.id);
     }
   }

Plus a module-scope memo so repeated calls with the same snapshot object are free:

const snapshotIndexCache = new WeakMap<
  MessageTreeSnapshot<UIMessage>,
  ReturnType<typeof computeSnapshotIndexes>
>();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 156 - 175, Refactor
getSnapshotIndexes to avoid rebuilding indexes during repeated lookups: add a
module-scope WeakMap cache keyed by the MessageTreeSnapshot object, return the
cached result when available, and store newly computed indexes before returning.
While computing, append message IDs to childrenByParentId arrays without copying
the existing array on every insertion, and ensure
findLeafDfsToRightFromSnapshot, getMessageSiblingInfo, and getParallelGroupInfo
reuse the cached indexes.

409-414: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Align setTreeSnapshot visible-message preservation with setAllMessages.

setTreeSnapshot merges currentVisibleMessages as [], so any message on the visible path that has not yet been persisted into allMessages is discarded when the snapshot is applied. If this can run during streaming/submitted, preserve state.messages there, matching setAllMessages; otherwise add a comment why snapshots are never applied mid-stream.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.ts` around lines 409 - 414, Update
setTreeSnapshot’s mergeTreeMessages call to pass the current visible messages
from state.messages instead of an empty array, preserving unpersisted messages
during streaming or submitted states and matching setAllMessages behavior; if
snapshots are guaranteed not to apply mid-stream, document that invariant at
this call site instead.
apps/chat/lib/stores/with-threads.test.ts (2)

366-381: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Coverage gap: roots whose metadata.parentMessageId points outside the tree.

All roots here have parentMessageId: null, so getMessageSiblingInfo's parentById[...] ?? getMetadataParentId(...) fallback happens to resolve to null either way. A message whose metadata parent is absent from allMessages is treated as a root by buildTreeSnapshotFromMessages but the fallback then resolves to the dangling id, yielding empty siblings (see the store comment on lines 544-546). Add a case for that shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.test.ts` around lines 366 - 381, The test
setup currently covers only roots with null metadata parents; add a case in the
sibling-navigation test using a message whose metadata.parentMessageId
references an ID absent from allMessages. Verify buildTreeSnapshotFromMessages
and getMessageSiblingInfo treat that message as a root and return the expected
root siblings rather than an empty list, preserving the existing root
assertions.

49-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Stub omits status, so the streaming code path is never exercised.

setAllMessages branches on state.status === "streaming" | "submitted" to keep the visible path authoritative and preserve in-flight messages — a central behavior of this refactor — but the stub has no status, so every test runs the non-streaming branch. Add status to the stub and a case that syncs stale server data while streaming, asserting messages is untouched while allMessages/treeSnapshot absorb the server nodes.

♻️ Stub addition
         ({
           _messageIndex: { update: () => undefined },
           _memoizedSelectors: new Map(),
           _throttledMessages: initialMessages,
           messages: initialMessages,
+          status: "ready",
           setMessages: (messages: ChatMessage[]) => set({ messages }),
         }) as unknown as BaseChatStoreState<ChatMessage>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/chat/lib/stores/with-threads.test.ts` around lines 49 - 57, Update the
BaseChatStoreState stub in with-threads.test.ts to include a status field, then
add a streaming-status test for setAllMessages that supplies stale server data
and verifies visible messages remain unchanged while allMessages and
treeSnapshot incorporate the server nodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/chat/lib/stores/with-threads.ts`:
- Around line 477-480: Update the nextVisibleThread selection around
currentLeafId and buildThreadFromSnapshot so an empty currentVisibleMessages
path falls back to the snapshot’s rightmost leaf and adopts the merged server
history. Preserve the existing currentLeafId-based behavior when visible
messages are present, and keep the empty result only when no suitable snapshot
leaf exists.
- Around line 544-546: In both parent lookups within
apps/chat/lib/stores/with-threads.ts at lines 544-546 and 567-569, replace
nullish-coalescing fallback with a presence check against
getSnapshotIndexes(state.treeSnapshot).parentById, preserving stored null values
for genuine roots and only falling back to getMetadataParentId(message) or
metadata?.parentMessageId when the message ID is absent; preferably centralize
this logic in one shared helper.
- Around line 210-226: Update buildThreadFromSnapshot to remove the hard-coded
100-iteration limit, track visited message IDs (or bound traversal by the
snapshot node count) for cycle safety, and preserve every acyclic ancestor.
Replace thread.unshift with push during traversal, then reverse the collected
thread before returning; use a descriptive named bound if a snapshot-size limit
is chosen.
- Line 488: Update the setAllMessages flow so it does not assign
threadInitialMessages; preserve that field as a remount seed updated only during
thread switches or store initialization. Keep setAllMessages responsible for
current message state, and retain the existing thread-switch assignment to
nextVisibleThread.
- Around line 424-430: Update setTreeSnapshot’s nextVisibleThread logic so a
null mergedSnapshot.cursorId does not clear the existing visible messages when
mergedMessages still contains thread content; preserve the current
messages/_throttledMessages or derive the visible path from the merged snapshot.
Add or adjust the emitsNullCursorWithMessages test to verify allMessages and the
visible thread remain consistent.

---

Nitpick comments:
In `@apps/chat/lib/stores/with-threads.test.ts`:
- Around line 366-381: The test setup currently covers only roots with null
metadata parents; add a case in the sibling-navigation test using a message
whose metadata.parentMessageId references an ID absent from allMessages. Verify
buildTreeSnapshotFromMessages and getMessageSiblingInfo treat that message as a
root and return the expected root siblings rather than an empty list, preserving
the existing root assertions.
- Around line 49-57: Update the BaseChatStoreState stub in with-threads.test.ts
to include a status field, then add a streaming-status test for setAllMessages
that supplies stale server data and verifies visible messages remain unchanged
while allMessages and treeSnapshot incorporate the server nodes.

In `@apps/chat/lib/stores/with-threads.ts`:
- Around line 504-518: Extract the duplicated metadata-preservation branch into
a shared helper such as withPreservedMetadata near mergeMessageIntoMap. Update
mergeMessageIntoMap and addMessageToTree to call this helper, preserving
existing metadata only when the incoming message omits it and otherwise
returning the incoming message unchanged.
- Around line 362-366: In the initialization flow surrounding the returned
object, compute buildTreeSnapshotFromMessages(base.messages) once in an
initialSnapshot variable declared before the object literal, then reuse it for
treeSnapshot and getSnapshotSignature. Update rebuildMap to receive the same
snapshot explicitly instead of triggering its default snapshot construction.
- Around line 248-252: Replace the full JSON.stringify implementation in
getSnapshotSignature with a compact, stable signature derived from cursorId and
each message’s id, parentId, and revision marker. Avoid serializing message
parts or metadata, and ensure the derived ordering is deterministic so
structurally identical snapshots deduplicate consistently at the existing call
sites.
- Around line 113-127: Update the message-grouping loop in
buildTreeSnapshotFromMessages to retrieve or initialize each parent’s sibling
array once, then append the current message with push instead of creating a
copied array on every insert. Preserve the existing parentId assignment and
subsequent compareSiblingMessages sorting.
- Around line 332-353: Make the parentById input to mergeTreeMessages required
instead of defaulting to an empty object, and update setAllMessages to provide
the appropriate parent map (or derive it from the merged messages) before
addFallbackMetadataToMessages runs. Preserve setTreeSnapshot’s existing map flow
so fallback metadata is populated consistently and cannot silently no-op when
the argument is omitted.
- Around line 156-175: Refactor getSnapshotIndexes to avoid rebuilding indexes
during repeated lookups: add a module-scope WeakMap cache keyed by the
MessageTreeSnapshot object, return the cached result when available, and store
newly computed indexes before returning. While computing, append message IDs to
childrenByParentId arrays without copying the existing array on every insertion,
and ensure findLeafDfsToRightFromSnapshot, getMessageSiblingInfo, and
getParallelGroupInfo reuse the cached indexes.
- Around line 409-414: Update setTreeSnapshot’s mergeTreeMessages call to pass
the current visible messages from state.messages instead of an empty array,
preserving unpersisted messages during streaming or submitted states and
matching setAllMessages behavior; if snapshots are guaranteed not to apply
mid-stream, document that invariant at this call site instead.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 685a148b-4d14-480e-a8b9-f43ab6def433

📥 Commits

Reviewing files that changed from the base of the PR and between 3116998 and 9584bff.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • apps/chat/lib/stores/with-threads.test.ts
  • apps/chat/lib/stores/with-threads.ts
  • apps/chat/package.json

Comment thread apps/chat/lib/stores/with-threads.ts
Comment thread apps/chat/lib/stores/with-threads.ts
Comment thread apps/chat/lib/stores/with-threads.ts
Comment thread apps/chat/lib/stores/with-threads.ts Outdated
Comment thread apps/chat/lib/stores/with-threads.ts Outdated
@FranciscoMoretti

Copy link
Copy Markdown
Owner Author

Superseded by the same branch rebased onto the new package compatibility layer. Review continues in #267; this PR remains available for its existing discussion history.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant