Skip to content

Commit b7e2669

Browse files
committed
fix(chat): preserve quick-click prefetch intent
1 parent 51fff88 commit b7e2669

2 files changed

Lines changed: 61 additions & 5 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/chat-navigation-link/chat-navigation-link.test.tsx

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,23 @@ describe('ChatNavigationLink', () => {
140140

141141
it('prefetches before direct mouse clicks and completed touch taps', () => {
142142
const link = renderLink()
143+
linkPrefetch.mockClear()
143144

144-
act(() => link.dispatchEvent(pointerEvent('pointerdown', 'mouse', { button: 0 })))
145+
act(() => {
146+
link.dispatchEvent(pointerEvent('pointerdown', 'mouse', { button: 0 }))
147+
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
148+
})
145149

146150
expect(linkPrefetch).toHaveBeenLastCalledWith(true)
147151
expect(prefetchQuery).toHaveBeenCalledTimes(1)
148152

149153
act(() => link.dispatchEvent(new FocusEvent('focusout', { bubbles: true })))
154+
linkPrefetch.mockClear()
150155
prefetchQuery.mockClear()
151-
act(() => link.dispatchEvent(pointerEvent('pointerup', 'touch', { button: 0 })))
156+
act(() => {
157+
link.dispatchEvent(pointerEvent('pointerup', 'touch', { button: 0 }))
158+
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
159+
})
152160

153161
expect(linkPrefetch).toHaveBeenLastCalledWith(true)
154162
expect(prefetchQuery).toHaveBeenCalledTimes(1)
@@ -200,6 +208,45 @@ describe('ChatNavigationLink', () => {
200208
expect(prefetchQuery).not.toHaveBeenCalled()
201209
})
202210

211+
it('clears prior intent when a persistent row changes route roles', () => {
212+
const renderRouteRole = (isCurrentRoute: boolean) => {
213+
act(() => {
214+
root.render(
215+
<QueryClientProvider client={queryClient}>
216+
<ChatNavigationLink
217+
chatId='chat-1'
218+
href='/workspace/ws-1/chat/chat-1'
219+
isCurrentRoute={isCurrentRoute}
220+
>
221+
Open chat
222+
</ChatNavigationLink>
223+
</QueryClientProvider>
224+
)
225+
})
226+
}
227+
228+
renderRouteRole(false)
229+
const link = container.querySelector('a')
230+
if (!link) throw new Error('chat link not rendered')
231+
act(() => {
232+
link.dispatchEvent(pointerEvent('pointerdown', 'mouse', { button: 0 }))
233+
link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
234+
})
235+
expect(linkPrefetch).toHaveBeenLastCalledWith(true)
236+
237+
renderRouteRole(true)
238+
renderRouteRole(false)
239+
240+
expect(linkPrefetch).toHaveBeenLastCalledWith(false)
241+
prefetchQuery.mockClear()
242+
const destinationLink = container.querySelector('a')
243+
if (!destinationLink) throw new Error('destination link not rendered')
244+
act(() => {
245+
destinationLink.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
246+
})
247+
expect(prefetchQuery).toHaveBeenCalledTimes(1)
248+
})
249+
203250
it('does not prefetch when the click is canceled or opens another browsing context', () => {
204251
const canceledLink = renderLink()
205252

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/chat-navigation-link/chat-navigation-link.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,12 @@ interface ChatNavigationLinkProps extends Omit<ComponentProps<typeof Link>, 'hre
3535
isCurrentRoute?: boolean
3636
}
3737

38-
export function ChatNavigationLink({
38+
export function ChatNavigationLink(props: ChatNavigationLinkProps) {
39+
const routeRole = props.isCurrentRoute ? 'current' : 'destination'
40+
return <IntentAwareChatNavigationLink key={`${props.chatId}:${routeRole}`} {...props} />
41+
}
42+
43+
function IntentAwareChatNavigationLink({
3944
chatId,
4045
href,
4146
isCurrentRoute = false,
@@ -52,6 +57,7 @@ export function ChatNavigationLink({
5257
}: ChatNavigationLinkProps) {
5358
const queryClient = useQueryClient()
5459
const prefetchTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
60+
const navigationIntentRef = useRef(false)
5561
const [shouldPrefetchRoute, setShouldPrefetchRoute] = useState(false)
5662

5763
const cancelScheduledPrefetch = useCallback(() => {
@@ -69,6 +75,7 @@ export function ChatNavigationLink({
6975
const prefetchForIntent = () => {
7076
cancelScheduledPrefetch()
7177
if (isCurrentRoute) return
78+
navigationIntentRef.current = true
7279
setShouldPrefetchRoute(true)
7380
prefetchHistory()
7481
}
@@ -95,6 +102,7 @@ export function ChatNavigationLink({
95102
onMouseLeave={(event) => {
96103
onMouseLeave?.(event)
97104
cancelScheduledPrefetch()
105+
navigationIntentRef.current = false
98106
setShouldPrefetchRoute(false)
99107
}}
100108
onFocus={(event) => {
@@ -104,6 +112,7 @@ export function ChatNavigationLink({
104112
onBlur={(event) => {
105113
onBlur?.(event)
106114
cancelScheduledPrefetch()
115+
navigationIntentRef.current = false
107116
setShouldPrefetchRoute(false)
108117
}}
109118
onPointerDown={(event) => {
@@ -121,6 +130,7 @@ export function ChatNavigationLink({
121130
onPointerCancel={(event) => {
122131
onPointerCancel?.(event)
123132
cancelScheduledPrefetch()
133+
navigationIntentRef.current = false
124134
setShouldPrefetchRoute(false)
125135
}}
126136
onTouchStart={onTouchStart}
@@ -134,8 +144,7 @@ export function ChatNavigationLink({
134144
!event.altKey
135145
) {
136146
cancelScheduledPrefetch()
137-
if (!isCurrentRoute && !shouldPrefetchRoute) prefetchHistory()
138-
setShouldPrefetchRoute(false)
147+
if (!isCurrentRoute && !navigationIntentRef.current) prefetchHistory()
139148
}
140149
}}
141150
/>

0 commit comments

Comments
 (0)