Skip to content

Commit 32fdad5

Browse files
NoomStuffjuliusmarmingeclaude
authored
fix(web): timeline minimap preview no longer steals hover or jumps around (#32151)
Co-authored-by: Julius Marminge <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
1 parent 4363d0b commit 32fdad5

2 files changed

Lines changed: 10 additions & 41 deletions

File tree

‎apps/web/src/components/chat/MessagesTimeline.logic.ts‎

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@ export function workEntryIsVisibleInGroup(
283283
}
284284
const TIMELINE_MINIMAP_ITEM_SPACING = 8;
285285
export const TIMELINE_MINIMAP_MIN_ITEMS = 2;
286-
const TIMELINE_MINIMAP_MAX_HEIGHT_CSS = "calc(100vh - 18rem)";
286+
// Relative to the timeline, not the viewport, so an open terminal shrinks the rail and
287+
// endpoint previews (centered on their strip) still fit above and below it.
288+
const TIMELINE_MINIMAP_MAX_HEIGHT_CSS = "max(4rem, calc(100% - 14.75rem))";
287289
const TIMELINE_MINIMAP_PERSISTENT_GUTTER = 48;
288290

289291
export interface WorkGroupScrollAnchor {
@@ -443,7 +445,6 @@ export function resolveTimelineMinimapHasPersistentGutter(
443445

444446
const TIMELINE_MINIMAP_HIT_STRIP_LEFT = 12;
445447
const TIMELINE_MINIMAP_HIT_STRIP_MAX_WIDTH = 40;
446-
const TIMELINE_MINIMAP_EXPANDED_HIT_STRIP_WIDTH = "22rem";
447448

448449
export function resolveTimelineMinimapHitStripWidth(
449450
viewportWidth: number,
@@ -465,13 +466,6 @@ export function resolveTimelineMinimapNavigationInteractive(collapsedWidth: numb
465466
return collapsedWidth >= TIMELINE_MINIMAP_NAVIGATION_REACH;
466467
}
467468

468-
export function resolveTimelineMinimapInteractiveWidth(
469-
collapsedWidth: number,
470-
expanded: boolean,
471-
): number | string {
472-
return expanded ? TIMELINE_MINIMAP_EXPANDED_HIT_STRIP_WIDTH : collapsedWidth;
473-
}
474-
475469
function computeElapsedMs(startIso: string, endIso: string): number | null {
476470
const start = Date.parse(startIso);
477471
const end = Date.parse(endIso);

‎apps/web/src/components/chat/TimelineMinimap.tsx‎

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,13 @@ import { Button } from "../ui/button";
55
import {
66
resolveTimelineMinimapHeightStyle,
77
resolveTimelineMinimapIndexFromPointer,
8-
resolveTimelineMinimapInteractiveWidth,
98
resolveTimelineMinimapNavigationInteractive,
109
resolveTimelineMinimapTopPercent,
1110
TIMELINE_MINIMAP_MIN_ITEMS,
1211
} from "./MessagesTimeline.logic";
1312
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
1413
import { cn } from "~/lib/utils";
1514

16-
function timelineMinimapEventTargetsPreview(target: EventTarget): boolean {
17-
return target instanceof Element && target.closest("[data-minimap-preview]") !== null;
18-
}
19-
2015
// A release outside the window can skip `mouseup`, so the next button-free
2116
// `mousemove` also counts.
2217
function onMouseButtonsReleased(onRelease: () => void): () => void {
@@ -74,14 +69,6 @@ export function TimelineMinimap({
7469
resolvedActiveIndex === null
7570
? 0
7671
: resolveTimelineMinimapTopPercent(resolvedActiveIndex, items.length);
77-
const activeTooltipTranslate =
78-
resolvedActiveIndex === null
79-
? "-50%"
80-
: resolvedActiveIndex === 0
81-
? "0%"
82-
: resolvedActiveIndex === items.length - 1
83-
? "-100%"
84-
: "-50%";
8572
const resolvedCurrentIndex =
8673
currentIndex !== null && currentIndex >= 0 && currentIndex < items.length ? currentIndex : null;
8774
const previousItem =
@@ -147,7 +134,7 @@ export function TimelineMinimap({
147134
}}
148135
style={{
149136
height: resolveTimelineMinimapHeightStyle(items.length),
150-
width: resolveTimelineMinimapInteractiveWidth(hitStripWidth, activeItem !== null),
137+
width: hitStripWidth,
151138
}}
152139
>
153140
<TimelineMinimapNavigationButton
@@ -160,12 +147,9 @@ export function TimelineMinimap({
160147
/>
161148
<button
162149
aria-label={`Jump to message: ${activeItem?.userText ?? "User message"}`}
163-
className="absolute inset-y-0 left-0 w-full cursor-pointer bg-transparent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
150+
className="absolute inset-y-0 left-0 w-full cursor-pointer bg-transparent before:absolute before:inset-x-0 before:-inset-y-1 before:content-[''] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring/70"
164151
onBlur={() => setActiveIndex(null)}
165152
onClick={(event) => {
166-
if (timelineMinimapEventTargetsPreview(event.target)) {
167-
return;
168-
}
169153
const nextIndex = resolveActiveIndexFromPointer(event);
170154
const selectedItem = nextIndex === null ? null : (items[nextIndex] ?? null);
171155
if (selectedItem) {
@@ -199,12 +183,7 @@ export function TimelineMinimap({
199183
if (event.buttons !== 0 && !pressedOnStripRef.current) return;
200184
updateActiveIndexFromPointer(event);
201185
}}
202-
onMouseDown={(event) => {
203-
if (timelineMinimapEventTargetsPreview(event.target)) {
204-
return;
205-
}
206-
event.preventDefault();
207-
}}
186+
onMouseDown={(event) => event.preventDefault()}
208187
type="button"
209188
>
210189
<div className="absolute top-0 left-3 h-full w-px bg-border/15" />
@@ -245,14 +224,10 @@ export function TimelineMinimap({
245224
);
246225
})}
247226
{activeItem ? (
227+
// Display-only: it overlaps the message column, and the strip keeps tracking beneath it.
248228
<span
249-
className="pointer-events-auto absolute left-8 w-80 cursor-text select-text"
250-
data-minimap-preview
251-
onMouseMove={(event) => event.stopPropagation()}
252-
style={{
253-
top: `${activeTopPercent}%`,
254-
transform: `translateY(${activeTooltipTranslate})`,
255-
}}
229+
className="pointer-events-none absolute left-8 w-80 -translate-y-1/2"
230+
style={{ top: `${activeTopPercent}%` }}
256231
>
257232
<span className="dropdown-glass block rounded-xl p-3 text-left text-popover-foreground shadow-xl shadow-black/25">
258233
<span className="block max-w-full overflow-hidden text-ellipsis whitespace-nowrap text-sm font-medium leading-5">
@@ -311,7 +286,7 @@ function TimelineMinimapNavigationButton({
311286
className={cn(
312287
"absolute left-1 z-10 inline-flex -translate-x-1/2 opacity-0 transition-opacity duration-150 hover:opacity-100 focus-within:opacity-100",
313288
interactive ? "pointer-events-auto" : "pointer-events-none",
314-
previous ? "bottom-[calc(100%+2px)]" : "top-[calc(100%+2px)]",
289+
previous ? "bottom-[calc(100%+6px)]" : "top-[calc(100%+6px)]",
315290
)}
316291
/>
317292
}

0 commit comments

Comments
 (0)