Skip to content

[codex] Optimize thread loading and browser panels#3

Merged
Eccentric-jamaican merged 2 commits into
mainfrom
codex/optimize-thread-loading-browser-panels
Jun 11, 2026
Merged

[codex] Optimize thread loading and browser panels#3
Eccentric-jamaican merged 2 commits into
mainfrom
codex/optimize-thread-loading-browser-panels

Conversation

@Eccentric-jamaican

@Eccentric-jamaican Eccentric-jamaican commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Problem statement

The app had several user-visible reliability and latency issues in busy workspaces and long conversations:

  • Long threads could make the app feel blank or stuck because the route load waited on heavy thread content before the sidebar/projects felt ready.
  • Switching between sidebar threads could briefly show blank content, and lightweight snapshots could accidentally erase already-loaded thread bodies from client state.
  • Scrolling back through long thread history could snap back toward the bottom while the user was trying to read older messages.
  • Rendered markdown/code output could become visually garbled, with text overlapping aggressively.
  • Closing or switching away from the integrated browser panel could leave stale browser panel state around across project/thread changes.

A deeper issue also surfaced during investigation: the app did not need full thread details for every thread to make the sidebar useful, but some client paths treated omitted details as authoritative empty data.

What changed

  • Split initial route hydration into a lightweight bootstrap snapshot first, followed by focused thread-body loading for the selected thread.
  • Added a clear loading state for existing server threads whose body is still being fetched, instead of showing the empty new-thread landing.
  • Added preservation rules for lightweight and focused snapshots so omitted thread details do not wipe already-loaded messages, plans, checkpoints, or activity state.
  • Added authoritative-thread tracking so a focused snapshot can still intentionally update the thread it actually fetched.
  • Added request dedupe/normalization for snapshot calls so equivalent focused requests share the same in-flight WebSocket request.
  • Added sidebar hover/focus prefetch for thread bodies to reduce perceived latency when the user is about to switch threads.
  • Hardened long-message scroll behavior so user upward scrolling cancels pending stick-to-bottom behavior and avoids scroll snap-back.
  • Hardened markdown/Shiki codeblock CSS so highlighted output uses stable line boxes and horizontal scrolling instead of overlap.
  • Tightened integrated browser panel state handling across close, same-project switches, cross-project switches, and split-view state persistence.
  • Added focused regression coverage for snapshot preservation and split-view files/panel state.

Validation

  • bun run lint passed. Existing repo warnings remain, no errors.
  • bun run typecheck passed.
  • bun run --cwd apps/web test src/store.test.ts src/splitViewStore.test.ts src/wsNativeApi.test.ts passed.
  • Live app verification on the long DeepSeek thread showed projects/sidebar rows and the long thread body loaded correctly.
  • Direct WebSocket measurements on the running app:
    • Bootstrap snapshot: ~47 ms, 120,555 bytes, 24 projects, 100 threads, no message bodies.
    • Focused long-thread snapshot: ~42 ms, 171,574 bytes, 6 messages, 47,116 message-text characters.
  • Scroll stability check on the long thread showed zero snap-back after scrolling upward.

Notes

OpenCode draft prewarm was intentionally not added. Its session startup emits real provider/session events, so using it as casual prewarm before the user sends a message could create confusing session state. The PR keeps that path conservative.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added "Loading conversation..." placeholder while fetching conversations
    • Thread prefetching on sidebar hover for faster navigation
    • Improved split-view thread switching with automatic cross-project panel cleanup
  • Bug Fixes

    • Refined message auto-scroll behavior for better user control
    • Fixed scroll position adjustments when reviewing conversation history
    • Prevented panel state carryover when switching between projects
  • Style

    • Enhanced markdown code-block rendering for improved readability
  • Tests

    • Added split-view store tests
    • Added read model synchronization tests

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Eccentric-jamaican, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 36 minutes and 22 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0795c5e6-5716-45c5-ac9c-2ea9850d233e

📥 Commits

Reviewing files that changed from the base of the PR and between a594006 and 7eaa733.

📒 Files selected for processing (5)
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/chat/MessagesTimeline.tsx
  • apps/web/src/routes/_chat.$threadId.tsx
  • apps/web/src/splitViewStore.test.ts
📝 Walkthrough

Walkthrough

This PR implements a multi-layer thread detail hydration and synchronization system. It adds a store-level preservation mechanism to retain previously-loaded thread details during lightweight snapshots, deduplicates snapshot requests, updates root hydration with options-aware syncing, adds chat-route focused snapshots for split-view support, improves UI loading states and scroll behavior, implements sidebar hover-based thread prefetching, and updates bootstrap and disposal flows to preserve thread details consistently.

Changes

Thread Detail Hydration and Synchronization

Layer / File(s) Summary
Store-level thread detail preservation mechanism
apps/web/src/store.ts, apps/web/src/store.test.ts
Introduces SyncServerReadModelOptions interface to control whether thread details (messages, proposedPlans, turnDiffSummaries, activities) are preserved when snapshots are empty and threads are not authoritative. Updates syncServerReadModel function signature and store wiring to accept and forward options, with tests validating preservation during lightweight and focused snapshot syncs.
Snapshot request deduplication
apps/web/src/wsNativeApi.ts
Adds snapshotRequestKey helper and getOrchestrationSnapshot function to coalesce concurrent identical snapshot requests by normalized key, removing the entry on settlement. Updates orchestration API's getSnapshot to use the deduplication helper instead of direct requests.
Root route hydration with thread detail preservation
apps/web/src/routes/__root.tsx
Computes authoritative thread IDs from focused snapshot inputs and determines preservation semantics based on snapshot mode. Distinguishes focused vs bootstrap hydration, branches error handling, and calls syncServerReadModel with options to preserve thread details and mark authoritative IDs.
Chat UI improvements: loading state and scroll behavior
apps/web/src/components/ChatView.tsx, apps/web/src/components/chat/MessagesTimeline.tsx, apps/web/src/index.css
Adds shouldShowThreadBodyLoading to render a "Loading conversation..." placeholder when server thread exists with no server messages yet. Refines scroll-to-bottom to compute and store bottom position, reset intent flags. Adjusts onMessagesScroll/onMessagesWheel logic to toggle auto-scroll based on near-bottom status and user input. Updates Markdown code-block CSS for block display, max-content sizing, and Shiki horizontal scrolling.
Chat route focused snapshot synchronization
apps/web/src/routes/_chat.$threadId.tsx
Maximizes pane using focused panel state instead of last-open. Detects project changes during thread switching and clears panel/files-open state to prevent cross-project carryover. Adds onBrowserPanelClosed callback to SingleChatSurface. Main view computes focused thread IDs (including split-view participants), fetches focused snapshot when thread details are missing, syncs with preservation, and wires callback to suppress browser re-open.
Sidebar thread detail prefetching on hover
apps/web/src/components/Sidebar.tsx
Adds 160ms-delayed prefetch of thread details on sidebar row hover/focus. Suppresses prefetch for routed/archived threads, threads without latest turn, or threads with existing messages. De-dupes by prefetch key, prevents concurrent in-flight requests per thread. Mouse enter/focus triggers schedule, mouse leave/blur cancels. Unmount clears all scheduled timeouts.
Bootstrap and disposal thread synchronization
apps/web/src/components/ChatView.tsx, apps/web/src/hooks/useDisposableThreadLifecycle.ts
Updates bootstrap snapshot syncs (implementation-thread and new-project threads) and disposable thread lifecycle to pass { preserveThreadDetails: true } option, aligning preservation semantics across thread creation and disposal.
Split view store improvements and testing
apps/web/src/splitViewStore.ts, apps/web/src/splitViewStore.test.ts
Refactors setPanePanelState early-return condition to separate filesOpen check onto its own line. Adds Vitest test suite with in-memory Storage mock, creates split view, updates left-pane filesOpen, and asserts state changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 A snapshot dance of threads so neat,
Details preserved, where worlds meet.
Prefetch on hover, scroll refined,
Split-views in sync, one cohesive mind.
Bootstrap and blend, no loss in sight—
Hydration flows smooth, the load is light!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main changes: optimizing thread loading (lightweight snapshots, body prefetching) and browser panel handling (cross-project/split-view state fixes), which aligns with the core objectives of the PR.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/optimize-thread-loading-browser-panels

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 and usage tips.

@Eccentric-jamaican Eccentric-jamaican marked this pull request as ready for review June 11, 2026 13:46

@coderabbitai coderabbitai 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.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/src/components/ChatView.tsx (1)

5260-5280: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve non-authoritative thread bodies on the focused post-create sync.

This success path still applies a focused snapshot with syncServerReadModel(snapshot) only. Focused snapshots are exactly the payloads that can omit details for other threads, so this can still blank out already-loaded messages/plans/activities right before navigation.

Suggested fix
       .then(() => api.orchestration.getSnapshot({ mode: "focused", threadId: nextThreadId }))
       .then((snapshot) => {
-        syncServerReadModel(snapshot);
+        syncServerReadModel(snapshot, {
+          preserveThreadDetails: true,
+          authoritativeThreadIds: [nextThreadId],
+        });
         return navigate({
           to: "/$threadId",
           params: { threadId: nextThreadId },
         });
       })
🤖 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/web/src/components/ChatView.tsx` around lines 5260 - 5280, The
focused-snapshot success path currently calls syncServerReadModel(snapshot)
which may omit other threads' bodies and wipe loaded content; update the success
branch to call syncServerReadModel(snapshot, { preserveThreadDetails: true })
(the same option used in the bootstrap fallback) before calling navigate so
non-authoritative thread bodies are preserved; locate the promise chain where
.then((snapshot) => { syncServerReadModel(snapshot); return navigate({ to:
"/$threadId", params: { threadId: nextThreadId } }); }) and add the
preserveThreadDetails option to that syncServerReadModel call.
🧹 Nitpick comments (1)
apps/web/src/splitViewStore.test.ts (1)

4-27: ⚡ Quick win

Consider adding type annotation for compile-time verification.

The createMemoryStorage implementation is correct, but adding a satisfies Storage annotation would provide compile-time verification that all Storage interface methods are properly implemented, following the pattern established in browserPaneStore.test.ts (lines 3-26).

✨ Suggested type annotation
-function createMemoryStorage(): Storage {
+function createMemoryStorage() {
   const values = new Map<string, string>();
 
-  return {
+  return {
     get length() {
       return values.size;
     },
     clear() {
       values.clear();
     },
     getItem(key: string) {
       return values.get(key) ?? null;
     },
     key(index: number) {
       return [...values.keys()][index] ?? null;
     },
     removeItem(key: string) {
       values.delete(key);
     },
     setItem(key: string, value: string) {
       values.set(key, value);
     },
-  };
+  } satisfies Storage;
 }
🤖 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/web/src/splitViewStore.test.ts` around lines 4 - 27, The
createMemoryStorage function should be annotated to ensure it conforms to the
DOM Storage interface; update the return expression so the object literal
satisfies the Storage type (use the same "satisfies Storage" pattern used in
browserPaneStore.test.ts) to get compile-time verification that getItem,
setItem, removeItem, key, clear and length match the Storage signature for the
createMemoryStorage function.
🤖 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/web/src/components/chat/MessagesTimeline.tsx`:
- Around line 3694-3705: The bottom-threshold logic in
rowVirtualizer.shouldAdjustScrollPositionOnItemSizeChange ignores the incoming
delta and uses instance.getTotalSize() (which is post-resize), causing
misclassification; change the signature to use the received delta (remove the
underscore) and compute the pre-resize total as previousTotal =
instance.getTotalSize() - delta, then compute remainingDistance = previousTotal
- (scrollOffset + viewportHeight) and use that in isReadingOlderHistory (still
comparing to AUTO_SCROLL_BOTTOM_THRESHOLD_PX), keeping the other checks
(changedItemIsAboveViewport and instance.scrollDirection) unchanged.

In `@apps/web/src/components/ChatView.tsx`:
- Around line 3583-3591: The code is treating any pointer down as a manual
scroll intent because isPointerScrollActiveRef.current is set on pointer down;
update the logic so mere pointer presses don’t disable auto-follow: stop setting
isPointerScrollActiveRef in the pointerdown handler and instead set it only when
a pointermove/pan occurs (or when a pointer-move delta exceeds a small
threshold), then keep the existing branch that checks shouldAutoScrollRef,
pendingUserScrollUpIntentRef and isPointerScrollActiveRef (and call
cancelPendingStickToBottom as before) so only actual pointer-driven scrolling
(not clicks/selection) disables stick-to-bottom; adjust the pointer handlers
where isPointerScrollActiveRef and pendingUserScrollUpIntentRef are mutated to
implement this change.

In `@apps/web/src/components/Sidebar.tsx`:
- Around line 2667-2681: Pinned threads are missing the hover/focus prefetch
handlers, so update renderPinnedThreadRow to mirror renderThreadRow's wiring:
add onMouseEnter to call scheduleThreadDetailPrefetch(thread), onMouseLeave to
call cancelThreadDetailPrefetch(thread.id) and clearArchiveConfirm(thread.id),
onFocusCapture to scheduleThreadDetailPrefetch(thread), and onBlurCapture to
check relatedTarget same as the current logic and then
cancelThreadDetailPrefetch(thread.id) and clearArchiveConfirm(thread.id); ensure
you reference the same helper functions scheduleThreadDetailPrefetch,
cancelThreadDetailPrefetch, and clearArchiveConfirm and use the same
event.relatedTarget containment check as in renderThreadRow.

In `@apps/web/src/routes/_chat`.$threadId.tsx:
- Around line 1601-1620: The current guard uses threadExists and
focusedSnapshotNeedsThreadDetails which can skip loading sibling server-thread
details when the routed thread is draft-only; instead, change the condition to
check whether any focused thread exists in store.threads (e.g., inspect
store.threads for at least one focused id) and only return if no focused threads
at all, and when calling api.orchestration.getSnapshot use a server-backed
threadId (resolve a server thread id from focusedSnapshotThreadIds or omit
threadId so the server fetch targets all focusedThreadIds) rather than the
routed draft-only threadId; update the conditional around
threadsHydrated/hydrationStatus to use this new existence check and adjust the
getSnapshot call arguments (threadId vs threadIds) so server pane messages are
fetched.
- Around line 1072-1083: The pane state reset currently only triggers when
projectChanged is true, but it should also reset when the current pane is empty
(currentPaneThreadId === null); update the condition used in setPanePanelState
so that it resets panel and filesOpen when either projectChanged is true OR
currentPaneThreadId is null. Locate the computation of
currentPaneProjectId/nextPaneProjectId and the projectChanged boolean, then
change the setPanePanelState call (adjacent to replacePaneThread) to use
(projectChanged || currentPaneThreadId === null) to decide whether to spread {
panel: null, filesOpen: false }.
- Around line 992-998: When serializing the focused pane state, preserve the
files rail by carrying over focusedPanelState.filesOpen instead of dropping it;
update the branches that return { panel: "browser" } and { panel: "diff", diff:
"1" } to also include filesOpen: focusedPanelState.filesOpen, and in the
fallback return ensure filesOpen is preserved (e.g., return { filesOpen:
focusedPanelState.filesOpen } when present) so filesOpen isn't lost when
maximizing/minimizing.

---

Outside diff comments:
In `@apps/web/src/components/ChatView.tsx`:
- Around line 5260-5280: The focused-snapshot success path currently calls
syncServerReadModel(snapshot) which may omit other threads' bodies and wipe
loaded content; update the success branch to call syncServerReadModel(snapshot,
{ preserveThreadDetails: true }) (the same option used in the bootstrap
fallback) before calling navigate so non-authoritative thread bodies are
preserved; locate the promise chain where .then((snapshot) => {
syncServerReadModel(snapshot); return navigate({ to: "/$threadId", params: {
threadId: nextThreadId } }); }) and add the preserveThreadDetails option to that
syncServerReadModel call.

---

Nitpick comments:
In `@apps/web/src/splitViewStore.test.ts`:
- Around line 4-27: The createMemoryStorage function should be annotated to
ensure it conforms to the DOM Storage interface; update the return expression so
the object literal satisfies the Storage type (use the same "satisfies Storage"
pattern used in browserPaneStore.test.ts) to get compile-time verification that
getItem, setItem, removeItem, key, clear and length match the Storage signature
for the createMemoryStorage function.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 02435800-5dc5-4a2d-b623-a98787f74d19

📥 Commits

Reviewing files that changed from the base of the PR and between 4a4710e and a594006.

📒 Files selected for processing (12)
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/chat/MessagesTimeline.tsx
  • apps/web/src/hooks/useDisposableThreadLifecycle.ts
  • apps/web/src/index.css
  • apps/web/src/routes/__root.tsx
  • apps/web/src/routes/_chat.$threadId.tsx
  • apps/web/src/splitViewStore.test.ts
  • apps/web/src/splitViewStore.ts
  • apps/web/src/store.test.ts
  • apps/web/src/store.ts
  • apps/web/src/wsNativeApi.ts

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/web/src/components/Sidebar.tsx
Comment thread apps/web/src/routes/_chat.$threadId.tsx Outdated
Comment thread apps/web/src/routes/_chat.$threadId.tsx Outdated
Comment thread apps/web/src/routes/_chat.$threadId.tsx Outdated
@Eccentric-jamaican Eccentric-jamaican merged commit c66a836 into main Jun 11, 2026
1 of 2 checks passed
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