|
|
@@ -42,6 +42,7 @@ import { Binary } from "@opencode-ai/util/binary"
|
|
|
import { retry } from "@opencode-ai/util/retry"
|
|
|
import { playSound, soundSrc } from "@/utils/sound"
|
|
|
import { createAim } from "@/utils/aim"
|
|
|
+import { setNavigate } from "@/utils/notification-click"
|
|
|
import { Worktree as WorktreeState } from "@/utils/worktree"
|
|
|
|
|
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
|
|
@@ -59,11 +60,11 @@ import { useLanguage, type Locale } from "@/context/language"
|
|
|
import {
|
|
|
childMapByParent,
|
|
|
displayName,
|
|
|
+ effectiveWorkspaceOrder,
|
|
|
errorMessage,
|
|
|
getDraggableId,
|
|
|
latestRootSession,
|
|
|
sortedRootSessions,
|
|
|
- syncWorkspaceOrder,
|
|
|
workspaceKey,
|
|
|
} from "./layout/helpers"
|
|
|
import { collectOpenProjectDeepLinks, deepLinkEvent, drainPendingDeepLinks } from "./layout/deep-links"
|
|
|
@@ -107,6 +108,7 @@ export default function Layout(props: ParentProps) {
|
|
|
const notification = useNotification()
|
|
|
const permission = usePermission()
|
|
|
const navigate = useNavigate()
|
|
|
+ setNavigate(navigate)
|
|
|
const providers = useProviders()
|
|
|
const dialog = useDialog()
|
|
|
const command = useCommand()
|
|
|
@@ -481,21 +483,6 @@ export default function Layout(props: ParentProps) {
|
|
|
return projects.find((p) => p.worktree === root)
|
|
|
})
|
|
|
|
|
|
- createEffect(
|
|
|
- on(
|
|
|
- () => ({ ready: pageReady(), project: currentProject() }),
|
|
|
- (value) => {
|
|
|
- if (!value.ready) return
|
|
|
- const project = value.project
|
|
|
- if (!project) return
|
|
|
- const last = server.projects.last()
|
|
|
- if (last === project.worktree) return
|
|
|
- server.projects.touch(project.worktree)
|
|
|
- },
|
|
|
- { defer: true },
|
|
|
- ),
|
|
|
- )
|
|
|
-
|
|
|
createEffect(
|
|
|
on(
|
|
|
() => ({ ready: pageReady(), layoutReady: layoutReady(), dir: params.dir, list: layout.projects.list() }),
|
|
|
@@ -554,29 +541,17 @@ export default function Layout(props: ParentProps) {
|
|
|
return layout.sidebar.workspaces(project.worktree)()
|
|
|
})
|
|
|
|
|
|
- createEffect(() => {
|
|
|
- if (!pageReady()) return
|
|
|
- if (!layoutReady()) return
|
|
|
+ const visibleSessionDirs = createMemo(() => {
|
|
|
const project = currentProject()
|
|
|
- if (!project) return
|
|
|
-
|
|
|
- const local = project.worktree
|
|
|
- const dirs = [project.worktree, ...(project.sandboxes ?? [])]
|
|
|
- const existing = store.workspaceOrder[project.worktree]
|
|
|
- const merged = syncWorkspaceOrder(local, dirs, existing)
|
|
|
- if (!existing) {
|
|
|
- setStore("workspaceOrder", project.worktree, merged)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (merged.length !== existing.length) {
|
|
|
- setStore("workspaceOrder", project.worktree, merged)
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (merged.some((d, i) => d !== existing[i])) {
|
|
|
- setStore("workspaceOrder", project.worktree, merged)
|
|
|
- }
|
|
|
+ if (!project) return [] as string[]
|
|
|
+ if (!workspaceSetting()) return [project.worktree]
|
|
|
+
|
|
|
+ const activeDir = currentDir()
|
|
|
+ return workspaceIds(project).filter((directory) => {
|
|
|
+ const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
|
|
|
+ const active = directory === activeDir
|
|
|
+ return expanded || active
|
|
|
+ })
|
|
|
})
|
|
|
|
|
|
createEffect(() => {
|
|
|
@@ -593,25 +568,17 @@ export default function Layout(props: ParentProps) {
|
|
|
})
|
|
|
|
|
|
const currentSessions = createMemo(() => {
|
|
|
- const project = currentProject()
|
|
|
- if (!project) return [] as Session[]
|
|
|
const now = Date.now()
|
|
|
- if (workspaceSetting()) {
|
|
|
- const dirs = workspaceIds(project)
|
|
|
- const activeDir = currentDir()
|
|
|
- const result: Session[] = []
|
|
|
- for (const dir of dirs) {
|
|
|
- const expanded = store.workspaceExpanded[dir] ?? dir === project.worktree
|
|
|
- const active = dir === activeDir
|
|
|
- if (!expanded && !active) continue
|
|
|
- const [dirStore] = globalSync.child(dir, { bootstrap: true })
|
|
|
- const dirSessions = sortedRootSessions(dirStore, now)
|
|
|
- result.push(...dirSessions)
|
|
|
- }
|
|
|
- return result
|
|
|
+ const dirs = visibleSessionDirs()
|
|
|
+ if (dirs.length === 0) return [] as Session[]
|
|
|
+
|
|
|
+ const result: Session[] = []
|
|
|
+ for (const dir of dirs) {
|
|
|
+ const [dirStore] = globalSync.child(dir, { bootstrap: true })
|
|
|
+ const dirSessions = sortedRootSessions(dirStore, now)
|
|
|
+ result.push(...dirSessions)
|
|
|
}
|
|
|
- const [projectStore] = globalSync.child(project.worktree)
|
|
|
- return sortedRootSessions(projectStore, now)
|
|
|
+ return result
|
|
|
})
|
|
|
|
|
|
type PrefetchQueue = {
|
|
|
@@ -826,7 +793,6 @@ export default function Layout(props: ParentProps) {
|
|
|
}
|
|
|
|
|
|
navigateToSession(session)
|
|
|
- queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`))
|
|
|
}
|
|
|
|
|
|
function navigateSessionByUnseen(offset: number) {
|
|
|
@@ -861,7 +827,6 @@ export default function Layout(props: ParentProps) {
|
|
|
}
|
|
|
|
|
|
navigateToSession(session)
|
|
|
- queueMicrotask(() => scrollToSession(session.id, `${session.directory}:${session.id}`))
|
|
|
return
|
|
|
}
|
|
|
}
|
|
|
@@ -1094,34 +1059,90 @@ export default function Layout(props: ParentProps) {
|
|
|
return meta?.worktree ?? directory
|
|
|
}
|
|
|
|
|
|
+ function activeProjectRoot(directory: string) {
|
|
|
+ return currentProject()?.worktree ?? projectRoot(directory)
|
|
|
+ }
|
|
|
+
|
|
|
+ function touchProjectRoute() {
|
|
|
+ const root = currentProject()?.worktree
|
|
|
+ if (!root) return
|
|
|
+ if (server.projects.last() !== root) server.projects.touch(root)
|
|
|
+ return root
|
|
|
+ }
|
|
|
+
|
|
|
+ function rememberSessionRoute(directory: string, id: string, root = activeProjectRoot(directory)) {
|
|
|
+ setStore("lastProjectSession", root, { directory, id, at: Date.now() })
|
|
|
+ return root
|
|
|
+ }
|
|
|
+
|
|
|
+ function clearLastProjectSession(root: string) {
|
|
|
+ if (!store.lastProjectSession[root]) return
|
|
|
+ setStore(
|
|
|
+ "lastProjectSession",
|
|
|
+ produce((draft) => {
|
|
|
+ delete draft[root]
|
|
|
+ }),
|
|
|
+ )
|
|
|
+ }
|
|
|
+
|
|
|
+ function syncSessionRoute(directory: string, id: string, root = activeProjectRoot(directory)) {
|
|
|
+ rememberSessionRoute(directory, id, root)
|
|
|
+ notification.session.markViewed(id)
|
|
|
+ const expanded = untrack(() => store.workspaceExpanded[directory])
|
|
|
+ if (expanded === false) {
|
|
|
+ setStore("workspaceExpanded", directory, true)
|
|
|
+ }
|
|
|
+ requestAnimationFrame(() => scrollToSession(id, `${directory}:${id}`))
|
|
|
+ return root
|
|
|
+ }
|
|
|
+
|
|
|
async function navigateToProject(directory: string | undefined) {
|
|
|
if (!directory) return
|
|
|
const root = projectRoot(directory)
|
|
|
server.projects.touch(root)
|
|
|
const project = layout.projects.list().find((item) => item.worktree === root)
|
|
|
- const dirs = Array.from(new Set([root, ...(store.workspaceOrder[root] ?? []), ...(project?.sandboxes ?? [])]))
|
|
|
+ let dirs = project
|
|
|
+ ? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
|
|
|
+ : [root]
|
|
|
+ const canOpen = (value: string | undefined) => {
|
|
|
+ if (!value) return false
|
|
|
+ return dirs.some((item) => workspaceKey(item) === workspaceKey(value))
|
|
|
+ }
|
|
|
+ const refreshDirs = async (target?: string) => {
|
|
|
+ if (!target || target === root || canOpen(target)) return canOpen(target)
|
|
|
+ const listed = await globalSDK.client.worktree
|
|
|
+ .list({ directory: root })
|
|
|
+ .then((x) => x.data ?? [])
|
|
|
+ .catch(() => [] as string[])
|
|
|
+ dirs = effectiveWorkspaceOrder(root, [root, ...listed], store.workspaceOrder[root])
|
|
|
+ return canOpen(target)
|
|
|
+ }
|
|
|
const openSession = async (target: { directory: string; id: string }) => {
|
|
|
+ if (!canOpen(target.directory)) return false
|
|
|
const resolved = await globalSDK.client.session
|
|
|
.get({ sessionID: target.id })
|
|
|
.then((x) => x.data)
|
|
|
.catch(() => undefined)
|
|
|
- const next = resolved?.directory ? resolved : target
|
|
|
- setStore("lastProjectSession", root, { directory: next.directory, id: next.id, at: Date.now() })
|
|
|
- navigateWithSidebarReset(`/${base64Encode(next.directory)}/session/${next.id}`)
|
|
|
+ if (!resolved?.directory) return false
|
|
|
+ if (!canOpen(resolved.directory)) return false
|
|
|
+ setStore("lastProjectSession", root, { directory: resolved.directory, id: resolved.id, at: Date.now() })
|
|
|
+ navigateWithSidebarReset(`/${base64Encode(resolved.directory)}/session/${resolved.id}`)
|
|
|
+ return true
|
|
|
}
|
|
|
|
|
|
const projectSession = store.lastProjectSession[root]
|
|
|
if (projectSession?.id) {
|
|
|
- await openSession(projectSession)
|
|
|
- return
|
|
|
+ await refreshDirs(projectSession.directory)
|
|
|
+ const opened = await openSession(projectSession)
|
|
|
+ if (opened) return
|
|
|
+ clearLastProjectSession(root)
|
|
|
}
|
|
|
|
|
|
const latest = latestRootSession(
|
|
|
dirs.map((item) => globalSync.child(item, { bootstrap: false })[0]),
|
|
|
Date.now(),
|
|
|
)
|
|
|
- if (latest) {
|
|
|
- await openSession(latest)
|
|
|
+ if (latest && (await openSession(latest))) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -1137,8 +1158,7 @@ export default function Layout(props: ParentProps) {
|
|
|
),
|
|
|
Date.now(),
|
|
|
)
|
|
|
- if (fetched) {
|
|
|
- await openSession(fetched)
|
|
|
+ if (fetched && (await openSession(fetched))) {
|
|
|
return
|
|
|
}
|
|
|
|
|
|
@@ -1195,11 +1215,28 @@ export default function Layout(props: ParentProps) {
|
|
|
}
|
|
|
|
|
|
function closeProject(directory: string) {
|
|
|
- const index = layout.projects.list().findIndex((x) => x.worktree === directory)
|
|
|
- const next = layout.projects.list()[index + 1]
|
|
|
+ const list = layout.projects.list()
|
|
|
+ const index = list.findIndex((x) => x.worktree === directory)
|
|
|
+ const active = currentProject()?.worktree === directory
|
|
|
+ if (index === -1) return
|
|
|
+ const next = list[index + 1]
|
|
|
+
|
|
|
+ if (!active) {
|
|
|
+ layout.projects.close(directory)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!next) {
|
|
|
+ layout.projects.close(directory)
|
|
|
+ navigate("/")
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ navigateWithSidebarReset(`/${base64Encode(next.worktree)}/session`)
|
|
|
layout.projects.close(directory)
|
|
|
- if (next) navigateToProject(next.worktree)
|
|
|
- else navigate("/")
|
|
|
+ queueMicrotask(() => {
|
|
|
+ void navigateToProject(next.worktree)
|
|
|
+ })
|
|
|
}
|
|
|
|
|
|
function toggleProjectWorkspaces(project: LocalProject) {
|
|
|
@@ -1240,9 +1277,17 @@ export default function Layout(props: ParentProps) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- const deleteWorkspace = async (root: string, directory: string) => {
|
|
|
+ const deleteWorkspace = async (root: string, directory: string, leaveDeletedWorkspace = false) => {
|
|
|
if (directory === root) return
|
|
|
|
|
|
+ const current = currentDir()
|
|
|
+ const currentKey = workspaceKey(current)
|
|
|
+ const deletedKey = workspaceKey(directory)
|
|
|
+ const shouldLeave = leaveDeletedWorkspace || (!!params.dir && currentKey === deletedKey)
|
|
|
+ if (!leaveDeletedWorkspace && shouldLeave) {
|
|
|
+ navigateWithSidebarReset(`/${base64Encode(root)}/session`)
|
|
|
+ }
|
|
|
+
|
|
|
setBusy(directory, true)
|
|
|
|
|
|
const result = await globalSDK.client.worktree
|
|
|
@@ -1260,6 +1305,10 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
if (!result) return
|
|
|
|
|
|
+ if (workspaceKey(store.lastProjectSession[root]?.directory ?? "") === workspaceKey(directory)) {
|
|
|
+ clearLastProjectSession(root)
|
|
|
+ }
|
|
|
+
|
|
|
globalSync.set(
|
|
|
"project",
|
|
|
produce((draft) => {
|
|
|
@@ -1273,8 +1322,18 @@ export default function Layout(props: ParentProps) {
|
|
|
layout.projects.close(directory)
|
|
|
layout.projects.open(root)
|
|
|
|
|
|
- if (params.dir && currentDir() === directory) {
|
|
|
- navigateToProject(root)
|
|
|
+ if (shouldLeave) return
|
|
|
+
|
|
|
+ const nextCurrent = currentDir()
|
|
|
+ const nextKey = workspaceKey(nextCurrent)
|
|
|
+ const project = layout.projects.list().find((item) => item.worktree === root)
|
|
|
+ const dirs = project
|
|
|
+ ? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
|
|
|
+ : [root]
|
|
|
+ const valid = dirs.some((item) => workspaceKey(item) === nextKey)
|
|
|
+
|
|
|
+ if (params.dir && projectRoot(nextCurrent) === root && !valid) {
|
|
|
+ navigateWithSidebarReset(`/${base64Encode(root)}/session`)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -1377,8 +1436,12 @@ export default function Layout(props: ParentProps) {
|
|
|
})
|
|
|
|
|
|
const handleDelete = () => {
|
|
|
+ const leaveDeletedWorkspace = !!params.dir && workspaceKey(currentDir()) === workspaceKey(props.directory)
|
|
|
+ if (leaveDeletedWorkspace) {
|
|
|
+ navigateWithSidebarReset(`/${base64Encode(props.root)}/session`)
|
|
|
+ }
|
|
|
dialog.close()
|
|
|
- void deleteWorkspace(props.root, props.directory)
|
|
|
+ void deleteWorkspace(props.root, props.directory, leaveDeletedWorkspace)
|
|
|
}
|
|
|
|
|
|
const description = () => {
|
|
|
@@ -1486,26 +1549,42 @@ export default function Layout(props: ParentProps) {
|
|
|
)
|
|
|
}
|
|
|
|
|
|
+ const activeRoute = {
|
|
|
+ session: "",
|
|
|
+ sessionProject: "",
|
|
|
+ }
|
|
|
+
|
|
|
createEffect(
|
|
|
on(
|
|
|
- () => ({ ready: pageReady(), dir: params.dir, id: params.id }),
|
|
|
- (value) => {
|
|
|
- if (!value.ready) return
|
|
|
- const dir = value.dir
|
|
|
- const id = value.id
|
|
|
- if (!dir || !id) return
|
|
|
+ () => [pageReady(), params.dir, params.id, currentProject()?.worktree] as const,
|
|
|
+ ([ready, dir, id]) => {
|
|
|
+ if (!ready || !dir) {
|
|
|
+ activeRoute.session = ""
|
|
|
+ activeRoute.sessionProject = ""
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
const directory = decode64(dir)
|
|
|
if (!directory) return
|
|
|
- const at = Date.now()
|
|
|
- setStore("lastProjectSession", projectRoot(directory), { directory, id, at })
|
|
|
- notification.session.markViewed(id)
|
|
|
- const expanded = untrack(() => store.workspaceExpanded[directory])
|
|
|
- if (expanded === false) {
|
|
|
- setStore("workspaceExpanded", directory, true)
|
|
|
+
|
|
|
+ const root = touchProjectRoute() ?? activeProjectRoot(directory)
|
|
|
+
|
|
|
+ if (!id) {
|
|
|
+ activeRoute.session = ""
|
|
|
+ activeRoute.sessionProject = ""
|
|
|
+ return
|
|
|
}
|
|
|
- requestAnimationFrame(() => scrollToSession(id, `${directory}:${id}`))
|
|
|
+
|
|
|
+ const session = `${dir}/${id}`
|
|
|
+ if (session !== activeRoute.session) {
|
|
|
+ activeRoute.session = session
|
|
|
+ activeRoute.sessionProject = syncSessionRoute(directory, id, root)
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ if (root === activeRoute.sessionProject) return
|
|
|
+ activeRoute.sessionProject = rememberSessionRoute(directory, id, root)
|
|
|
},
|
|
|
- { defer: true },
|
|
|
),
|
|
|
)
|
|
|
|
|
|
@@ -1516,40 +1595,29 @@ export default function Layout(props: ParentProps) {
|
|
|
|
|
|
const loadedSessionDirs = new Set<string>()
|
|
|
|
|
|
- createEffect(() => {
|
|
|
- const project = currentProject()
|
|
|
- const workspaces = workspaceSetting()
|
|
|
- const next = new Set<string>()
|
|
|
- if (!project) {
|
|
|
- loadedSessionDirs.clear()
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- if (workspaces) {
|
|
|
- const activeDir = currentDir()
|
|
|
- const dirs = [project.worktree, ...(project.sandboxes ?? [])]
|
|
|
- for (const directory of dirs) {
|
|
|
- const expanded = store.workspaceExpanded[directory] ?? directory === project.worktree
|
|
|
- const active = directory === activeDir
|
|
|
- if (!expanded && !active) continue
|
|
|
- next.add(directory)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (!workspaces) {
|
|
|
- next.add(project.worktree)
|
|
|
- }
|
|
|
+ createEffect(
|
|
|
+ on(
|
|
|
+ visibleSessionDirs,
|
|
|
+ (dirs) => {
|
|
|
+ if (dirs.length === 0) {
|
|
|
+ loadedSessionDirs.clear()
|
|
|
+ return
|
|
|
+ }
|
|
|
|
|
|
- for (const directory of next) {
|
|
|
- if (loadedSessionDirs.has(directory)) continue
|
|
|
- globalSync.project.loadSessions(directory)
|
|
|
- }
|
|
|
+ const next = new Set(dirs)
|
|
|
+ for (const directory of next) {
|
|
|
+ if (loadedSessionDirs.has(directory)) continue
|
|
|
+ globalSync.project.loadSessions(directory)
|
|
|
+ }
|
|
|
|
|
|
- loadedSessionDirs.clear()
|
|
|
- for (const directory of next) {
|
|
|
- loadedSessionDirs.add(directory)
|
|
|
- }
|
|
|
- })
|
|
|
+ loadedSessionDirs.clear()
|
|
|
+ for (const directory of next) {
|
|
|
+ loadedSessionDirs.add(directory)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { defer: true },
|
|
|
+ ),
|
|
|
+ )
|
|
|
|
|
|
function handleDragStart(event: unknown) {
|
|
|
const id = getDraggableId(event)
|
|
|
@@ -1583,14 +1651,11 @@ export default function Layout(props: ParentProps) {
|
|
|
const extra = directory && directory !== local && !dirs.includes(directory) ? directory : undefined
|
|
|
const pending = extra ? WorktreeState.get(extra)?.status === "pending" : false
|
|
|
|
|
|
- const existing = store.workspaceOrder[project.worktree]
|
|
|
- if (!existing) return extra ? [...dirs, extra] : dirs
|
|
|
-
|
|
|
- const merged = syncWorkspaceOrder(local, dirs, existing)
|
|
|
- if (pending && extra) return [local, extra, ...merged.filter((directory) => directory !== local)]
|
|
|
- if (!extra) return merged
|
|
|
- if (pending) return merged
|
|
|
- return [...merged, extra]
|
|
|
+ const ordered = effectiveWorkspaceOrder(local, dirs, store.workspaceOrder[project.worktree])
|
|
|
+ if (pending && extra) return [local, extra, ...ordered.filter((item) => item !== local)]
|
|
|
+ if (!extra) return ordered
|
|
|
+ if (pending) return ordered
|
|
|
+ return [...ordered, extra]
|
|
|
}
|
|
|
|
|
|
const sidebarProject = createMemo(() => {
|
|
|
@@ -1623,7 +1688,11 @@ export default function Layout(props: ParentProps) {
|
|
|
const [item] = result.splice(fromIndex, 1)
|
|
|
if (!item) return
|
|
|
result.splice(toIndex, 0, item)
|
|
|
- setStore("workspaceOrder", project.worktree, result)
|
|
|
+ setStore(
|
|
|
+ "workspaceOrder",
|
|
|
+ project.worktree,
|
|
|
+ result.filter((directory) => workspaceKey(directory) !== workspaceKey(project.worktree)),
|
|
|
+ )
|
|
|
}
|
|
|
|
|
|
function handleWorkspaceDragEnd() {
|
|
|
@@ -1661,10 +1730,9 @@ export default function Layout(props: ParentProps) {
|
|
|
const existing = prev ?? []
|
|
|
const next = existing.filter((item) => {
|
|
|
const id = workspaceKey(item)
|
|
|
- if (id === root) return false
|
|
|
- return id !== key
|
|
|
+ return id !== root && id !== key
|
|
|
})
|
|
|
- return [local, created.directory, ...next]
|
|
|
+ return [created.directory, ...next]
|
|
|
})
|
|
|
|
|
|
globalSync.child(created.directory)
|
|
|
@@ -2015,7 +2083,11 @@ export default function Layout(props: ParentProps) {
|
|
|
onOpenSettings={openSettings}
|
|
|
helpLabel={() => language.t("sidebar.help")}
|
|
|
onOpenHelp={() => platform.openLink("https://opencode.ai/desktop-feedback")}
|
|
|
- renderPanel={() => <SidebarPanel project={currentProject()} />}
|
|
|
+ renderPanel={() => (
|
|
|
+ <Show when={currentProject()} keyed>
|
|
|
+ {(project) => <SidebarPanel project={project} />}
|
|
|
+ </Show>
|
|
|
+ )}
|
|
|
/>
|
|
|
</div>
|
|
|
<Show when={!layout.sidebar.opened() ? hoverProjectData()?.worktree : undefined} keyed>
|