Add sticky user message overlay#2703
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)
Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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 |
| const listState = listRef.current?.getState?.(); | ||
| const currentEntries = entriesRef.current; | ||
|
|
||
| setHiddenMessageIds((current) => { |
There was a problem hiding this comment.
🟡 Medium chat/StickyUserMessagesOverlay.tsx:97
Equal.equals(current, next) compares two plain JavaScript Set objects using Effect's Equal.equals. Plain Set instances don't implement Effect's Equal interface, so the comparison falls back to reference equality (===), which always returns false for distinct Set instances. This causes setHiddenMessageIds to always return a new Set on every scroll/resize measurement, triggering unnecessary re-renders even when the hidden IDs haven't changed.
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/chat/StickyUserMessagesOverlay.tsx around line 97:
`Equal.equals(current, next)` compares two plain JavaScript `Set` objects using Effect's `Equal.equals`. Plain `Set` instances don't implement Effect's `Equal` interface, so the comparison falls back to reference equality (`===`), which always returns `false` for distinct `Set` instances. This causes `setHiddenMessageIds` to always return a new `Set` on every scroll/resize measurement, triggering unnecessary re-renders even when the hidden IDs haven't changed.
Evidence trail:
1. `apps/web/src/components/chat/StickyUserMessagesOverlay.tsx` lines 97-119 (REVIEWED_COMMIT) — `setHiddenMessageIds` callback creates `new Set<MessageId>()` and uses `Equal.equals(current, next)` to avoid returning a new reference.
2. Effect library `packages/effect/src/Equal.ts` lines 38-88 (https://github.com/Effect-TS/effect) — `compareBoth` function: for plain objects without `Equal` symbol, falls through to check `structuralRegionState.enabled`; for non-Array/non-plain-Object types like Set, returns false.
3. `packages/effect/src/Utils.ts` lines 747-752 (Effect-TS/effect) — `structuralRegionState` defaults to `{ enabled: false, tester: undefined }`.
4. `git_grep` for `structuralRegion` in pingdotgg/t3code — zero results, confirming structural region is never enabled.
5. Line 8 of the file: `import * as Equal from "effect/Equal";` confirms it's the Effect library's Equal module.
There was a problem hiding this comment.
I checked this against the installed Effect version in this repo (effect@4.0.0-beta.59) and native Set instances are handled structurally, not by reference.
Equal.ts has an explicit self instanceof Set branch which delegates to compareSets.
Runtime check confirms:
Equal.equals(new Set(["a", "b"]), new Set(["b", "a"])) === true
That said, I’m happy to switch this to a local explicit MessageId set comparison helper if maintainers prefer that for readability in the scroll path.
There was a problem hiding this comment.
Sorry, I'm unable to act on this request because you do not have permissions within this repository.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces a new user-facing feature (sticky user message overlay) with substantial new logic including a 356-line component, new settings, and scroll tracking behavior. Additionally, an unresolved comment identifies a potential performance issue with unnecessary re-renders. New feature additions of this scope warrant human review. You can customize Macroscope's approvability policy. Learn more. |
What Changed
Added configurable, sticky user messages in the messages timeline
Why
Switching between multiple sessions I find that to be a great help for quickly grasping where the conversation left off. Click-to-scroll also doubles as a quick way to jump to the beginning of the agent turn in case the user wants to skim it.
UI Changes
This changes the chat timeline UI.
Before: user messages scroll out of view during long assistant responses.

After: recent user prompts remain visible as compact sticky bubbles while scrolling. The newest sticky bubble includes the timestamp. Clicking the bubble scrolls the view to the original position

Demo video:
https://github.com/user-attachments/assets/d92d0b37-da5a-4bd5-bbce-93e457b8cc49
Checklist
Note
Medium Risk
Medium risk because it introduces new persisted client settings and non-trivial scroll/viewport measurement logic that could affect timeline rendering and scrolling behavior.
Overview
Adds a sticky overlay in
MessagesTimelinethat can pin the last 1–2 user prompts as compact bubbles once their original rows scroll above the viewport, with click-to-scroll back to the source message.Introduces new client settings (
stickyUserMessageCount,stickyUserMessageMaxLines) with bounded schema defaults, wires them fromChatViewinto the timeline, and exposes controls plus reset/dirty-state handling in Settings.Updates desktop/web/local API/settings contract tests and adds browser-focused timeline tests to validate sticky visibility rules, metadata display, and scroll-to-source behavior.
Reviewed by Cursor Bugbot for commit 47ea97d. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add sticky user message overlay to chat transcript
StickyUserMessagesOverlaythat pins recent user messages to the top of the chat viewport when their source rows scroll above the visible area, with click-to-scroll navigation back to the original message.StickyUserMessageBubblewhich clamps message text to a configurable max lines and shows a+N linesindicator and timestamp on the newest visible bubble.stickyUserMessageCount(0–2, default 0) andstickyUserMessageMaxLines(1–3, default 2) to client settings with schema validation, defaults, and a settings UI in the General panel.useHiddenStickyUserMessageIds, which attaches scroll/resize listeners and usesrequestAnimationFrameto batch DOM measurements of source element positions.📊 Macroscope summarized 47ea97d. 6 files reviewed, 4 issues evaluated, 1 issue filtered, 1 comment posted
🗂️ Filtered Issues
apps/web/src/components/chat/MessagesTimeline.browser.tsx — 0 comments posted, 2 evaluated, 1 filtered
LegendListcomponent (lines 16-42) does not forward theonScrollprop to the rendered<div>. When the test at line 253 dispatchesnew Event("scroll")on[data-testid='legend-list'], the component'sonScrollhandler passed toLegendListis never invoked. This means the scroll-triggered visibility logic inuseHiddenStickyUserMessageIdsmay not execute, causing the test "shows a sticky user message only after its source row is above the transcript viewport" to potentially pass or fail for reasons unrelated to the actual scroll behavior. [ Failed validation ]