feat(web): vertical minimap for user messages#2348
feat(web): vertical minimap for user messages#2348akarabach wants to merge 22 commits intopingdotgg:mainfrom
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…nto feat/chat-user-messages-minimap
ApprovabilityVerdict: Needs human review This PR introduces a complete new feature — a vertical minimap for navigating user messages in chat — including new React components, logic modules, browser tests, and a user-facing settings toggle. New features with this scope warrant human review. There is also an unresolved comment about listener reattachment during streaming that should be addressed. You can customize Macroscope's approvability policy. Learn more. |
- Wrap expanded menu in an outer div carrying rounded-lg + overflow-hidden so the inner scrollbar no longer paints over the rounded corners on the right side. - Reduce menu corner radius from rounded-xl to rounded-lg to match other popover surfaces (combobox, menu, select). - Add 8px of asymmetric right padding to the chat list so the minimap has more visual breathing room from right-aligned user messages.
…tener Optional chaining on `getState?.()` only protects against `getState` being undefined, not against it returning undefined. Add a second `?.` so the `.listen` access short-circuits cleanly when the list isn't yet measured.
|
#1917 added this feature but was buggy last time i tested it. haven't checked it in a while |
I reviewed it a bit, and I think this PR is better because:
|
|
but works very well other than that! |
- Sample minimap dashes to fit the available column height - Add a setting to hide the chat minimap and restore it cleanly - Cover the new minimap selection behavior with tests
- Decode `ClientSettingsSchema` in desktop and web persistence tests - Simplify chat minimap trigger and strip layout so it can collapse correctly
- Prevent stale or sparse minimap indices from skipping entries - Render dash markers as spans instead of buttons - Add regression tests for clamping and unmeasured gaps
- Cap the initial unmeasured minimap render to avoid overflowing long threads - Update minimap logic tests for short and long threads
@juliusmarminge wdyt? 80 is nice, but, maybe amount of messages is better
I have one more fix, please let me know that u are not working on this branch right now ;) |
A short final prompt with no content below it never lets its top reach the viewport top, so the existing viewport-top rule kept an earlier prompt lit while the reader was plainly looking at the latest. Add an `isAtEnd` flag to the minimap state snapshot and short-circuit `computeActiveMinimapIndex` to the last entry when it's true. Adds two unit tests: the canonical at-end case, and a sanity-check that `isAtEnd=false` falls through to the existing viewport-top rule.
…akarabach/t3code into feat/chat-user-messages-minimap # Conflicts: # apps/web/src/components/chat/ChatMinimap.logic.ts # apps/web/src/components/chat/MessagesTimeline.logic.test.ts
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4447b00. Configure here.
| ], | ||
| ); | ||
| const rows = useStableRows(rawRows); | ||
| const minimapEntries = useMemo(() => selectUserMessageMinimapEntries(rows), [rows]); |
There was a problem hiding this comment.
Minimap entries reattach listeners every streaming chunk
Low Severity
selectUserMessageMinimapEntries allocates fresh entry objects on every call, so minimapEntries gets a new array identity whenever any row in useStableRows changes — including each assistant streaming chunk where user messages are unchanged. Because entries is in the active-tracking effect's dependency list, the native scroll listener and lastPositionUpdate subscription tear down and re-attach on every chunk, undermining the PR's "no per-frame work when nothing changes" claim. Stabilizing identity (e.g., shallow-equal memo on the entry list) would avoid the thrash.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 4447b00. Configure here.





Summary
When a user has a long chat - it make sense to jump between user messages and rollback to some point in time, minimap allows to do it very fast
scroll+ Legend ListlastPositionUpdate) — no rAF polling, no per-frame work when nothing changes.Demo
What changed
apps/web/src/components/chat/ChatMinimap.tsx(new)apps/web/src/components/chat/ChatMinimap.logic.ts(new)selectUserMessageMinimapEntries(rows)andcomputeActiveMinimapIndex(state, entries). Kept separate so both can be unit-tested without DOM.apps/web/src/components/chat/ChatMinimap.browser.tsx(new)apps/web/src/components/ui/preview-card.tsx(new)@base-ui/react/preview-card. Used here for built-indelay/closeDelayhover handling — no manual timer plumbing.apps/web/src/components/chat/MessagesTimeline.tsxuseMemoselecting minimap entries from the rendered rows, and the<ChatMinimap>render. No state/refs/effects added to the timeline.apps/web/src/components/chat/MessagesTimeline.logic.tsapps/web/src/components/chat/MessagesTimeline.logic.test.tsselectUserMessageMinimapEntriesandcomputeActiveMinimapIndex(viewport-top threshold + advance-to-next refinement, empty/unmeasured guards, terminal-context preview fallback).Architecture notes
Active-dash tracking is event-driven, not polled. Two listeners in
ChatMinimap:scrollonlistRef.current.getScrollableNode()— fires once per frame while scrolling.state.listen("lastPositionUpdate", …)— fires on Legend List (re)measurement. Required becausescrollLengthreads as0before the async layout pass, and we want to avoid the "first dash flashes active" jitter.Both paths call the same pure
computeActiveMinimapIndexhelper.setActiveIndexshort-circuits when the value is unchanged, so 60Hz scroll events don't translate into 60Hz React renders.Encapsulation.
MessagesTimelineowns nothing minimap-specific beyondconst minimapEntries = useMemo(() => selectUserMessageMinimapEntries(rows), [rows])and the JSX. All activation state lives inChatMinimap.Hover delays. Handled by Base UI's
<PreviewCard>(delay={60}/closeDelay={150}) — no manualsetTimeoutrefs, no cleanup-on-unmount effect.Menu placement. The expanded menu replaces the dashes strip in the same DOM position (no Portal/Positioner) so the rail stays visually anchored to the top-right corner regardless of state.
Test plan
bun --cwd apps/web run typecheck— cleanbun --cwd apps/web run test— 919 passingbun --cwd apps/web run test:browser— 148 passing (including 7 newChatMinimapbrowser tests)Out of scope / follow-ups
Note
Medium Risk
Adds a new interactive chat navigation UI plus a new persisted client setting, which can affect layout and settings serialization across web/desktop if defaults or schema handling are incorrect.
Overview
Adds a chat minimap rail to
MessagesTimelinethat renders a right-side strip of dashes (sampled to a max of 10 with a+Noverflow label) and expands on hover into a preview menu; clicking an item scrolls the underlyingLegendListto that user message and the active item tracks scroll/remeasure events.Introduces
ChatMinimap+ pure helpers inChatMinimap.logic.ts(selectUserMessageMinimapEntries,selectVisibleMinimapEntries,computeActiveMinimapIndex), adds browser + unit test coverage for the new behaviors, and adds aPreviewCardwrapper for hover open/close delays.Extends
ClientSettingsSchemawithhideChatMinimap(defaultfalse) and wires a toggle into Settings, updating related persistence tests to construct default settings via schema decoding.Reviewed by Cursor Bugbot for commit 69510a1. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add vertical minimap for user messages in the chat timeline
ChatMinimapcomponent rendered to the right of the message list inMessagesTimeline.tsx, showing a vertical strip of dashes representing user messages.PreviewCardmenu listing user message previews; clicking one scrolls to that message vialistRef.scrollToIndex.computeActiveMinimapIndex, which accounts for partial measurements and end-of-list state.+Noverflow indicator.hideChatMinimapboolean flag (defaultfalse) is added toClientSettingsSchemaand exposed as a toggle in the General settings panel.Macroscope summarized 69510a1.