Skip to content

Commit 7948de1

Browse files
authored
app: prefer using useLocation instead of window.location (#15989)
1 parent f363904 commit 7948de1

8 files changed

Lines changed: 50 additions & 44 deletions

File tree

bun.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/app/src/pages/layout/sidebar-items.tsx

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
import { A, useNavigate, useParams } from "@solidjs/router"
2-
import { useGlobalSync } from "@/context/global-sync"
3-
import { useLanguage } from "@/context/language"
4-
import { useLayout, type LocalProject, getAvatarColors } from "@/context/layout"
5-
import { useNotification } from "@/context/notification"
6-
import { usePermission } from "@/context/permission"
7-
import { base64Encode } from "@opencode-ai/util/encode"
1+
import type { Message, Session, TextPart, UserMessage } from "@opencode-ai/sdk/v2/client"
82
import { Avatar } from "@opencode-ai/ui/avatar"
93
import { HoverCard } from "@opencode-ai/ui/hover-card"
104
import { Icon } from "@opencode-ai/ui/icon"
115
import { IconButton } from "@opencode-ai/ui/icon-button"
126
import { MessageNav } from "@opencode-ai/ui/message-nav"
137
import { Spinner } from "@opencode-ai/ui/spinner"
148
import { Tooltip } from "@opencode-ai/ui/tooltip"
9+
import { base64Encode } from "@opencode-ai/util/encode"
1510
import { getFilename } from "@opencode-ai/util/path"
16-
import { type Message, type Session, type TextPart, type UserMessage } from "@opencode-ai/sdk/v2/client"
17-
import { For, Match, Show, Switch, createMemo, onCleanup, type Accessor, type JSX } from "solid-js"
11+
import { A, useNavigate, useParams } from "@solidjs/router"
12+
import { type Accessor, createMemo, For, type JSX, Match, onCleanup, Show, Switch } from "solid-js"
13+
import { useGlobalSync } from "@/context/global-sync"
14+
import { useLanguage } from "@/context/language"
15+
import { getAvatarColors, type LocalProject, useLayout } from "@/context/layout"
16+
import { useNotification } from "@/context/notification"
17+
import { usePermission } from "@/context/permission"
1818
import { agentColor } from "@/utils/agent"
19-
import { hasProjectPermissions } from "./helpers"
2019
import { sessionPermissionRequest } from "../session/composer/session-request-tree"
20+
import { hasProjectPermissions } from "./helpers"
2121

2222
const OPENCODE_PROJECT_ID = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
2323

@@ -231,7 +231,9 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
231231
const hoverEnabled = createMemo(() => (props.popover ?? true) && hoverAllowed())
232232
const isActive = createMemo(() => props.session.id === params.id)
233233

234-
const hoverPrefetch = { current: undefined as ReturnType<typeof setTimeout> | undefined }
234+
const hoverPrefetch = {
235+
current: undefined as ReturnType<typeof setTimeout> | undefined,
236+
}
235237
const cancelHoverPrefetch = () => {
236238
if (hoverPrefetch.current === undefined) return
237239
clearTimeout(hoverPrefetch.current)
@@ -300,17 +302,15 @@ export const SessionItem = (props: SessionItemProps): JSX.Element => {
300302
setHoverSession={props.setHoverSession}
301303
messageLabel={messageLabel}
302304
onMessageSelect={(message) => {
303-
if (!isActive()) {
305+
if (!isActive())
304306
layout.pendingMessage.set(`${base64Encode(props.session.directory)}/${props.session.id}`, message.id)
305-
navigate(`${props.slug}/session/${props.session.id}`)
306-
return
307-
}
308-
window.history.replaceState(null, "", `#message-${message.id}`)
309-
window.dispatchEvent(new HashChangeEvent("hashchange"))
307+
308+
navigate(`${props.slug}/session/${props.session.id}#message-${message.id}`)
310309
}}
311310
trigger={item}
312311
/>
313312
</Show>
313+
314314
<div
315315
class={`absolute ${props.dense ? "top-0.5 right-0.5" : "top-1 right-1"} flex items-center gap-0.5 transition-opacity`}
316316
classList={{
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const messageIdFromHash = (hash: string) => {
2+
const value = hash.startsWith("#") ? hash.slice(1) : hash
3+
const match = value.match(/^message-(.+)$/)
4+
if (!match) return
5+
return match[1]
6+
}

packages/app/src/pages/session/use-session-hash-scroll.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from "bun:test"
2-
import { messageIdFromHash } from "./use-session-hash-scroll"
2+
import { messageIdFromHash } from "./message-id-from-hash"
33

44
describe("messageIdFromHash", () => {
55
test("parses hash with leading #", () => {

packages/app/src/pages/session/use-session-hash-scroll.ts

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
import { createEffect, createMemo, onCleanup, onMount } from "solid-js"
2-
import { UserMessage } from "@opencode-ai/sdk/v2"
3-
4-
export const messageIdFromHash = (hash: string) => {
5-
const value = hash.startsWith("#") ? hash.slice(1) : hash
6-
const match = value.match(/^message-(.+)$/)
7-
if (!match) return
8-
return match[1]
9-
}
1+
import type { UserMessage } from "@opencode-ai/sdk/v2"
2+
import { useLocation, useNavigate } from "@solidjs/router"
3+
import { createEffect, createMemo, onMount } from "solid-js"
4+
import { messageIdFromHash } from "./message-id-from-hash"
5+
6+
export { messageIdFromHash } from "./message-id-from-hash"
107

118
export const useSessionHashScroll = (input: {
129
sessionKey: () => string
@@ -30,13 +27,18 @@ export const useSessionHashScroll = (input: {
3027
const messageIndex = createMemo(() => new Map(visibleUserMessages().map((m, i) => [m.id, i])))
3128
let pendingKey = ""
3229

30+
const location = useLocation()
31+
const navigate = useNavigate()
32+
3333
const clearMessageHash = () => {
34-
if (!window.location.hash) return
35-
window.history.replaceState(null, "", window.location.href.replace(/#.*$/, ""))
34+
if (!location.hash) return
35+
navigate(location.pathname + location.search, { replace: true })
3636
}
3737

3838
const updateHash = (id: string) => {
39-
window.history.replaceState(null, "", `#${input.anchor(id)}`)
39+
navigate(location.pathname + location.search + `#${input.anchor(id)}`, {
40+
replace: true,
41+
})
4042
}
4143

4244
const scrollToElement = (el: HTMLElement, behavior: ScrollBehavior) => {
@@ -53,6 +55,7 @@ export const useSessionHashScroll = (input: {
5355
}
5456

5557
const scrollToMessage = (message: UserMessage, behavior: ScrollBehavior = "smooth") => {
58+
console.log({ message, behavior })
5659
if (input.currentMessageId() !== message.id) input.setActiveMessage(message)
5760

5861
const index = messageIndex().get(message.id) ?? -1
@@ -100,7 +103,7 @@ export const useSessionHashScroll = (input: {
100103
}
101104

102105
const applyHash = (behavior: ScrollBehavior) => {
103-
const hash = window.location.hash.slice(1)
106+
const hash = location.hash.slice(1)
104107
if (!hash) {
105108
input.autoScroll.forceScrollToBottom()
106109
const el = input.scroller()
@@ -132,6 +135,7 @@ export const useSessionHashScroll = (input: {
132135
}
133136

134137
createEffect(() => {
138+
location.hash
135139
if (!input.sessionID() || !input.messagesReady()) return
136140
requestAnimationFrame(() => applyHash("auto"))
137141
})
@@ -155,7 +159,7 @@ export const useSessionHashScroll = (input: {
155159
}
156160
}
157161

158-
if (!targetId) targetId = messageIdFromHash(window.location.hash)
162+
if (!targetId) targetId = messageIdFromHash(location.hash)
159163
if (!targetId) return
160164
if (input.currentMessageId() === targetId) return
161165

@@ -171,14 +175,6 @@ export const useSessionHashScroll = (input: {
171175
if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
172176
window.history.scrollRestoration = "manual"
173177
}
174-
175-
const handler = () => {
176-
if (!input.sessionID() || !input.messagesReady()) return
177-
requestAnimationFrame(() => applyHash("auto"))
178-
}
179-
180-
window.addEventListener("hashchange", handler)
181-
onCleanup(() => window.removeEventListener("hashchange", handler))
182178
})
183179

184180
return {

packages/app/src/utils/notification-click.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export const setNavigate = (fn: (href: string) => void) => {
77
export const handleNotificationClick = (href?: string) => {
88
window.focus()
99
if (!href) return
10-
if (nav) nav(href)
11-
else window.location.assign(href)
10+
if (nav) return nav(href)
11+
console.warn("notification-click: navigate function not set, falling back to window.location.assign")
12+
window.location.assign(href)
1213
}

packages/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@solid-primitives/media": "2.3.3",
5252
"@solid-primitives/resize-observer": "2.1.3",
5353
"@solidjs/meta": "catalog:",
54+
"@solidjs/router": "catalog:",
5455
"dompurify": "3.3.1",
5556
"fuzzysort": "catalog:",
5657
"katex": "0.16.27",

packages/ui/src/components/message-part.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import { TextShimmer } from "./text-shimmer"
5252
import { AnimatedCountList } from "./tool-count-summary"
5353
import { ToolStatusTitle } from "./tool-status-title"
5454
import { animate } from "motion"
55+
import { useLocation } from "@solidjs/router"
5556

5657
function ShellSubmessage(props: { text: string; animate?: boolean }) {
5758
let widthRef: HTMLSpanElement | undefined
@@ -1471,6 +1472,7 @@ ToolRegistry.register({
14711472
render(props) {
14721473
const data = useData()
14731474
const i18n = useI18n()
1475+
const location = useLocation()
14741476
const childSessionId = () => props.metadata.sessionId as string | undefined
14751477
const title = createMemo(() => i18n.t("ui.tool.agent", { type: props.input.subagent_type || props.tool }))
14761478
const description = createMemo(() => {
@@ -1487,8 +1489,7 @@ ToolRegistry.register({
14871489
const direct = data.sessionHref?.(sessionId)
14881490
if (direct) return direct
14891491

1490-
if (typeof window === "undefined") return
1491-
const path = window.location.pathname
1492+
const path = location.pathname
14921493
const idx = path.indexOf("/session")
14931494
if (idx === -1) return
14941495
return `${path.slice(0, idx)}/session/${sessionId}`

0 commit comments

Comments
 (0)