Skip to content

feat: add chat outline navigation strip#1917

Closed
Abhijitam01 wants to merge 13 commits intopingdotgg:mainfrom
Abhijitam01:Abhijitam01/chat-panel-ui
Closed

feat: add chat outline navigation strip#1917
Abhijitam01 wants to merge 13 commits intopingdotgg:mainfrom
Abhijitam01:Abhijitam01/chat-panel-ui

Conversation

@Abhijitam01
Copy link
Copy Markdown

@Abhijitam01 Abhijitam01 commented Apr 11, 2026

Minimap-style strip beside chat content showing user message bars. Hover to expand popover with previews, click to scroll to message. Active messages highlighted via IntersectionObserver.

What Changed

Add a minimap-style outline strip on the right side of the chat, beside the message content area:

Small horizontal bars representing each user message
Currently visible messages highlighted with brighter bars (IntersectionObserver)
Hover the strip to reveal a popover with message text previews
Click any bar or popover entry to smooth-scroll to that message
Only shows user messages, hidden when there are none

Why

Its a simple ui change to make the ux better

UI Changes

Before :

Screenshot 2026-04-11 at 8 10 57 AM

After :

Screenshot 2026-04-11 at 8 14 09 AM Screenshot 2026-04-11 at 8 11 57 AM

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes

Note

Medium Risk
Adds a DOM-observer-driven UI overlay tied to the virtualized message list, which could introduce scroll/visibility bugs or performance regressions. Also tweaks primary environment WS URL derivation behavior, potentially affecting connection setup in unusual protocol cases.

Overview
Adds a minimap-style chat outline strip beside the message timeline that renders one marker per user message, highlights currently visible messages, and provides hover previews; clicking markers/previews smooth-scrolls to the corresponding message.

Wires the new ChatOutlinePanel into ChatView and adjusts layout positioning to support the overlay. Separately refactors primary environment target resolution by introducing deriveWsFromHttp() and using it for the window-origin wsBaseUrl.

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

Note

Add chat outline navigation strip with click-to-scroll and hover previews

  • Adds ChatOutlinePanel.tsx, a right-side minimap-style strip that marks user messages in the chat timeline, highlights currently visible messages via IntersectionObserver, and supports click-to-scroll (smooth, centered) and hover-to-expand previews.
  • Integrates the panel into ChatView.tsx by passing timelineEntries and legendListRef; the chat column container is set to position: relative to anchor the absolutely positioned strip.
  • Also introduces a deriveWsFromHttp helper in target.ts that converts HTTP(S) URLs to WS(S), replacing a prior code path that threw on unsupported protocols.

Macroscope summarized 63e0b02.

Minimap-style strip beside chat content showing user message bars.
Hover to expand popover with previews, click to scroll to message.
Active messages highlighted via IntersectionObserver.
@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: 8c91b8ab-9c4f-496f-af00-7e4b95cf08be

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

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.

@github-actions github-actions Bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Apr 11, 2026
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx Outdated
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx Outdated
@juliusmarminge
Copy link
Copy Markdown
Member

juliusmarminge commented Apr 11, 2026

how does it handle very long threads? this happened to me in conductor a while back xd
image

@macroscopeapp
Copy link
Copy Markdown
Contributor

macroscopeapp Bot commented Apr 11, 2026

Approvability

Verdict: Needs human review

1 blocking correctness issue found. This PR introduces a new user-facing chat outline navigation feature with non-trivial complexity. An unresolved high-severity bug has been identified: the component lacks a key prop, causing observers to target detached DOM elements after thread switches.

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

@Abhijitam01
Copy link
Copy Markdown
Author

how does it handle very long threads? this happened to me in conductor a while back xd image

this will only show upto 20 messages and then after that a scroll functionality

Abhijitam01 and others added 2 commits April 11, 2026 16:16
Use functional setState updaters to eliminate shared mutable Set between
IntersectionObserver and MutationObserver, preventing race conditions.
Batch removal updates into a single state update. Fix theme colors
(bg-foreground instead of bg-white) and add max-h-40 scroll cap.
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx Outdated
- Traverse nested children in removedNodes handler to find data-message-id
  on wrapper divs (mirrors the addedNodes traversal pattern)
- Move popover outside the overflow-y-auto scrollable strip container to
  prevent CSS clipping
@juliusmarminge
Copy link
Copy Markdown
Member

nothin happens when i click here:

CleanShot.2026-04-11.at.11.07.05.mp4

@Abhijitam01
Copy link
Copy Markdown
Author

its happening only when the messages passes the number 20 or from starting @juliusmarminge

@juliusmarminge
Copy link
Copy Markdown
Member

juliusmarminge commented Apr 11, 2026

idk. just tested on a large thread as that's where i thought i could find some bugs

but i tried clicking many of the "links" and none worked on this thread

Wire scrollToIndex through a shared ref so clicking any outline bar
scrolls to that message even when it's virtualized off-screen.
Pre-compute message-id→row-index Map for O(1) lookups.
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
Messages in the last ~8 rows are rendered directly in the DOM (not
tracked by the virtualizer). scrollToIndex with an out-of-bounds index
silently fails for these. Fall back to scrollIntoView when the target
index >= virtualizedRowCount.
@Abhijitam01
Copy link
Copy Markdown
Author

idk. just tested on a large thread as that's where i thought i could find some bugs

but i tried clicking many of the "links" and none worked on this thread

issue - When you click message #25 in the outline panel, our code looks up its index (25) and tells the virtualizer “hey, scroll to row 25.” But the virtualizer only knows about rows 0–22. It has no idea what row 25 is, so it just… does nothing. No error, no scroll, nothing happens.

Fix -
Before calling scrollToIndex, check if the message index is within the virtualizer’s range. If it’s a tail row (index >= virtualizedRowCount), skip the virtualizer entirely and use plain scrollIntoView — because those elements are already in the DOM, always.

Comment thread apps/web/src/components/chat/MessagesTimeline.tsx Outdated
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx
- Remove behavior: "smooth" from scrollToIndex — conflicts with
  virtualizer's re-measure correction for distant items, causing
  silent scroll failures. Matches existing codebase pattern.
- Return prev Set when IntersectionObserver fires without actual
  visibility changes, avoiding unnecessary re-renders during scroll.
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx Outdated
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx
- Remove unused OutlineEntry interface (dead code)
- Scope MutationObserver removal handler to user-message elements only,
  matching the IntersectionObserver's observation scope
- Skip no-op state updates in removal handler to prevent unnecessary
  re-renders when virtualizer recycles assistant rows
Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx
- Take upstream's LegendList approach, drop useVirtualizer
- Adapt ChatOutlinePanel props (scrollContainer=null for now)
- Remove duplicate /.well-known proxy entry in vite.config.ts
Null out hoverTimerRef after clearTimeout and inside the timeout
callback to prevent orphaned timers when handleMouseLeave fires
twice from both the wrapper and popover elements.
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/web/src/components/ChatView.tsx
Comment thread apps/web/src/components/ChatView.tsx Outdated
Replace hardcoded null props (scrollContainer={null}, onScrollToMessage={{
current: null }}) with a stable listRef prop backed by LegendList's ref.

- Derive scroll container via listRef.current.getScrollableNode()
- IntersectionObserver and MutationObserver now track visible messages
- Click-to-scroll uses querySelector on the real scroll container
- Stable ref prop no longer defeats React.memo on every render
/>

{/* Chat outline strip — aligned to right edge of message content */}
<ChatOutlinePanel
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.

🟠 High components/ChatView.tsx:3268

ChatOutlinePanel is rendered without a key prop, so it does not remount when the user switches threads. Its useEffect that captures the scroll container depends only on [listRef], and since the ref object identity is stable, the effect never re-runs after legendListRef.current is replaced. This leaves the component observing a detached DOM element; the IntersectionObserver, MutationObserver, and click handlers all target the wrong scroll container after a thread switch. Add key={activeThread.id} to force remount on thread change, matching the behavior of MessagesTimeline.

🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/ChatView.tsx around line 3268:

`ChatOutlinePanel` is rendered without a `key` prop, so it does not remount when the user switches threads. Its `useEffect` that captures the scroll container depends only on `[listRef]`, and since the ref object identity is stable, the effect never re-runs after `legendListRef.current` is replaced. This leaves the component observing a detached DOM element; the IntersectionObserver, MutationObserver, and click handlers all target the wrong scroll container after a thread switch. Add `key={activeThread.id}` to force remount on thread change, matching the behavior of `MessagesTimeline`.

Evidence trail:
apps/web/src/components/ChatView.tsx lines 3268-3271 (ChatOutlinePanel without key), line 3243 (MessagesTimeline with key={activeThread.id}), line 701 (legendListRef = useRef). apps/web/src/components/chat/ChatOutlinePanel.tsx lines 41-52 (useEffect with [listRef] dependency), lines 58-78 (IntersectionObserver using scrollContainer as root), lines 88-116 (MutationObserver observing scrollContainer), lines 122-131 (click handler using scrollContainer.querySelector).

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 is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 558e8fe. Configure here.

Comment thread apps/web/src/components/chat/ChatOutlinePanel.tsx Outdated
The useEffect that resolves the scroll container from LegendList's ref
depended only on [listRef], which is a stable useRef that never changes
identity. When the user switches threads, MessagesTimeline remounts via
key={activeThread.id}, but the effect never re-ran, leaving
scrollContainer pointing at the old detached DOM node.

Adding timelineEntries to the dependency array ensures the effect
re-runs on thread switch since the entries change with each thread.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Abhijitam01
Copy link
Copy Markdown
Author

@juliusmarminge after alot of changes , it is error free , can you please have a look


// Observe any user-message element currently in the DOM
const observeUserMessages = (root: Element) => {
root.querySelectorAll('[data-message-role="user"]').forEach((el) => {
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.

oh no

@juliusmarminge
Copy link
Copy Markdown
Member

#2348 is cleaner

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:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants