}): JSX.Element => (
@@ -259,7 +264,7 @@ const ProjectPreviewPanel = (props: {
class="flex w-full text-left justify-start text-text-base px-2 hover:bg-transparent active:bg-transparent"
onClick={() => {
props.ctx.openSidebar()
- props.setOpen(false)
+ props.ctx.onHoverOpenChanged(props.project.worktree, false)
if (props.selected()) return
props.ctx.navigateToProject(props.project.worktree)
}}
@@ -284,28 +289,16 @@ export const SortableProject = (props: {
const workspaceEnabled = createMemo(() => props.ctx.workspacesEnabled(props.project))
const dirs = createMemo(() => props.ctx.workspaceIds(props.project))
const [state, setState] = createStore({
- open: false,
menu: false,
suppressHover: false,
})
+ const isHoverProject = () => props.ctx.hoverProject() === props.project.worktree
const preview = createMemo(() => !props.mobile && props.ctx.sidebarOpened())
const overlay = createMemo(() => !props.mobile && !props.ctx.sidebarOpened())
- const active = createMemo(
- () => state.menu || (preview() ? state.open : overlay() && props.ctx.hoverProject() === props.project.worktree),
- )
+ const active = createMemo(() => state.menu || (preview() ? isHoverProject() : overlay() && isHoverProject()))
- createEffect(() => {
- if (preview()) return
- if (!state.open) return
- setState("open", false)
- })
-
- createEffect(() => {
- if (!selected()) return
- if (!state.open) return
- setState("open", false)
- })
+ const hoverOpen = () => isHoverProject() && preview() && !selected() && !state.menu
const label = (directory: string) => {
const [data] = globalSync.child(directory, { bootstrap: false })
@@ -346,7 +339,7 @@ export const SortableProject = (props: {
workspacesEnabled={props.ctx.workspacesEnabled}
closeProject={props.ctx.closeProject}
setMenu={(value) => setState("menu", value)}
- setOpen={(value) => setState("open", value)}
+ setOpen={(value) => props.ctx.onHoverOpenChanged(props.project.worktree, value)}
setSuppressHover={(value) => setState("suppressHover", value)}
language={language}
/>
@@ -357,7 +350,7 @@ export const SortableProject = (props: {
{
if (state.menu) return
if (value && state.suppressHover) return
- setState("open", value)
+ props.ctx.onHoverOpenChanged(props.project.worktree, value)
if (value) props.ctx.setHoverSession(undefined)
}}
>
@@ -381,7 +374,6 @@ export const SortableProject = (props: {
projectChildren={projectChildren}
workspaceSessions={workspaceSessions}
workspaceChildren={workspaceChildren}
- setOpen={(value) => setState("open", value)}
ctx={props.ctx}
language={language}
/>
diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx
index 6d29170081..2d3e31355a 100644
--- a/packages/app/src/pages/session.tsx
+++ b/packages/app/src/pages/session.tsx
@@ -1,5 +1,6 @@
import type { Project, UserMessage } from "@opencode-ai/sdk/v2"
import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { useMutation } from "@tanstack/solid-query"
import {
batch,
onCleanup,
@@ -40,7 +41,13 @@ import { useSync } from "@/context/sync"
import { useTerminal } from "@/context/terminal"
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
import { createSessionComposerState, SessionComposerRegion } from "@/pages/session/composer"
-import { createOpenReviewFile, createSessionTabs, createSizing, focusTerminalById } from "@/pages/session/helpers"
+import {
+ createOpenReviewFile,
+ createSessionTabs,
+ createSizing,
+ focusTerminalById,
+ shouldFocusTerminalOnKeyDown,
+} from "@/pages/session/helpers"
import { MessageTimeline } from "@/pages/session/message-timeline"
import { type DiffStyle, SessionReviewTab, type SessionReviewTabProps } from "@/pages/session/review-tab"
import { useSessionLayout } from "@/pages/session/session-layout"
@@ -239,14 +246,19 @@ function createSessionHistoryWindow(input: SessionHistoryWindowInput) {
if (added <= 0) return
if (growth <= 0) return
+
+ if (opts?.prefetch) {
+ const current = turnStart()
+ preserveScroll(() => setTurnStart(current + growth))
+ return
+ }
+
if (turnStart() !== start) return
- const reveal = !opts?.prefetch
const currentRendered = renderedUserMessages().length
const base = Math.max(beforeRendered, currentRendered)
- const target = reveal ? Math.min(afterVisible, base + turnBatch) : base
- const nextStart = Math.max(0, afterVisible - target)
- preserveScroll(() => setTurnStart(nextStart))
+ const target = Math.min(afterVisible, base + turnBatch)
+ preserveScroll(() => setTurnStart(Math.max(0, afterVisible - target)))
}
const onScrollerScroll = () => {
@@ -327,10 +339,7 @@ export default function Page() {
})
const [ui, setUi] = createStore({
- git: false,
pendingMessage: undefined as string | undefined,
- restoring: undefined as string | undefined,
- reverting: false,
reviewSnap: false,
scrollGesture: 0,
scroll: {
@@ -506,7 +515,6 @@ export default function Page() {
const [followup, setFollowup] = createStore({
items: {} as Record,
- sending: {} as Record,
failed: {} as Record,
paused: {} as Record,
edit: {} as Record<
@@ -644,25 +652,24 @@ export default function Page() {
globalSync.set("project", [...list, next])
}
+ const gitMutation = useMutation(() => ({
+ mutationFn: () => sdk.client.project.initGit(),
+ onSuccess: (x) => {
+ if (!x.data) return
+ upsert(x.data)
+ },
+ onError: (err) => {
+ showToast({
+ variant: "error",
+ title: language.t("common.requestFailed"),
+ description: formatServerError(err, language.t),
+ })
+ },
+ }))
+
function initGit() {
- if (ui.git) return
- setUi("git", true)
- void sdk.client.project
- .initGit()
- .then((x) => {
- if (!x.data) return
- upsert(x.data)
- })
- .catch((err) => {
- showToast({
- variant: "error",
- title: language.t("common.requestFailed"),
- description: formatServerError(err, language.t),
- })
- })
- .finally(() => {
- setUi("git", false)
- })
+ if (gitMutation.isPending) return
+ gitMutation.mutate()
}
let inputRef!: HTMLDivElement
@@ -854,7 +861,7 @@ export default function Page() {
// Prefer the open terminal over the composer when it can take focus
if (view().terminal.opened()) {
const id = terminal.active()
- if (id && focusTerminalById(id)) return
+ if (id && shouldFocusTerminalOnKeyDown(event) && focusTerminalById(id)) return
}
// Only treat explicit scroll keys as potential "user scroll" gestures.
@@ -961,8 +968,8 @@ export default function Page() {
{language.t("session.review.noVcs.createGit.description")}