Răsfoiți Sursa

fix(app): worktree selection should navigate to worktree

Adam 1 lună în urmă
părinte
comite
d1191675c6

+ 10 - 3
packages/app/src/components/session/session-new-view.tsx

@@ -23,11 +23,18 @@ export function NewSessionView(props: NewSessionViewProps) {
     if (options().includes(selection)) return selection
     if (options().includes(selection)) return selection
     return MAIN_WORKTREE
     return MAIN_WORKTREE
   })
   })
+  const projectRoot = createMemo(() => sync.project?.worktree ?? sync.data.path.directory)
+  const isWorktree = createMemo(() => {
+    const project = sync.project
+    if (!project) return false
+    return sync.data.path.directory !== project.worktree
+  })
 
 
   const label = (value: string) => {
   const label = (value: string) => {
     if (value === MAIN_WORKTREE) {
     if (value === MAIN_WORKTREE) {
+      if (isWorktree()) return "Main branch"
       const branch = sync.data.vcs?.branch
       const branch = sync.data.vcs?.branch
-      if (branch) return `Current branch (${branch})`
+      if (branch) return `Main branch (${branch})`
       return "Main branch"
       return "Main branch"
     }
     }
 
 
@@ -42,8 +49,8 @@ export function NewSessionView(props: NewSessionViewProps) {
       <div class="flex justify-center items-center gap-3">
       <div class="flex justify-center items-center gap-3">
         <Icon name="folder" size="small" />
         <Icon name="folder" size="small" />
         <div class="text-12-medium text-text-weak">
         <div class="text-12-medium text-text-weak">
-          {getDirectory(sync.data.path.directory)}
-          <span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
+          {getDirectory(projectRoot())}
+          <span class="text-text-strong">{getFilename(projectRoot())}</span>
         </div>
         </div>
       </div>
       </div>
       <div class="flex justify-center items-center gap-1">
       <div class="flex justify-center items-center gap-1">

+ 41 - 7
packages/app/src/context/layout.tsx

@@ -1,5 +1,5 @@
 import { createStore, produce } from "solid-js/store"
 import { createStore, produce } from "solid-js/store"
-import { batch, createMemo, onMount } from "solid-js"
+import { batch, createEffect, createMemo, onMount } from "solid-js"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { createSimpleContext } from "@opencode-ai/ui/context"
 import { useGlobalSync } from "./global-sync"
 import { useGlobalSync } from "./global-sync"
 import { useGlobalSDK } from "./global-sdk"
 import { useGlobalSDK } from "./global-sdk"
@@ -91,8 +91,8 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         : globalSync.data.project.find((x) => x.worktree === project.worktree)
         : globalSync.data.project.find((x) => x.worktree === project.worktree)
       return [
       return [
         {
         {
-          ...project,
           ...(metadata ?? {}),
           ...(metadata ?? {}),
+          ...project,
           icon: { url: metadata?.icon?.url, color: metadata?.icon?.color },
           icon: { url: metadata?.icon?.url, color: metadata?.icon?.color },
         },
         },
       ]
       ]
@@ -109,6 +109,41 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       return project
       return project
     }
     }
 
 
+    const roots = createMemo(() => {
+      const map = new Map<string, string>()
+      for (const project of globalSync.data.project) {
+        const sandboxes = project.sandboxes ?? []
+        for (const sandbox of sandboxes) {
+          map.set(sandbox, project.worktree)
+        }
+      }
+      return map
+    })
+
+    createEffect(() => {
+      const map = roots()
+      if (map.size === 0) return
+
+      const projects = server.projects.list()
+      const seen = new Set(projects.map((project) => project.worktree))
+
+      batch(() => {
+        for (const project of projects) {
+          const root = map.get(project.worktree)
+          if (!root) continue
+
+          server.projects.close(project.worktree)
+
+          if (!seen.has(root)) {
+            server.projects.open(root)
+            seen.add(root)
+          }
+
+          if (project.expanded) server.projects.expand(root)
+        }
+      })
+    })
+
     const enriched = createMemo(() => server.projects.list().flatMap(enrich))
     const enriched = createMemo(() => server.projects.list().flatMap(enrich))
     const list = createMemo(() => enriched().flatMap(colorize))
     const list = createMemo(() => enriched().flatMap(colorize))
 
 
@@ -125,11 +160,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
       projects: {
       projects: {
         list,
         list,
         open(directory: string) {
         open(directory: string) {
-          if (server.projects.list().find((x) => x.worktree === directory)) {
-            return
-          }
-          globalSync.project.loadSessions(directory)
-          server.projects.open(directory)
+          const root = roots().get(directory) ?? directory
+          if (server.projects.list().find((x) => x.worktree === root)) return
+          globalSync.project.loadSessions(root)
+          server.projects.open(root)
         },
         },
         close(directory: string) {
         close(directory: string) {
           server.projects.close(directory)
           server.projects.close(directory)

+ 25 - 5
packages/app/src/pages/session.tsx

@@ -1,4 +1,4 @@
-import { For, onCleanup, Show, Match, Switch, createMemo, createEffect, on, batch } from "solid-js"
+import { For, onCleanup, Show, Match, Switch, createMemo, createEffect, on } from "solid-js"
 import { createMediaQuery } from "@solid-primitives/media"
 import { createMediaQuery } from "@solid-primitives/media"
 import { Dynamic } from "solid-js/web"
 import { Dynamic } from "solid-js/web"
 import { useLocal } from "@/context/local"
 import { useLocal } from "@/context/local"
@@ -24,7 +24,7 @@ import { useSync } from "@/context/sync"
 import { useTerminal, type LocalPTY } from "@/context/terminal"
 import { useTerminal, type LocalPTY } from "@/context/terminal"
 import { useLayout } from "@/context/layout"
 import { useLayout } from "@/context/layout"
 import { Terminal } from "@/components/terminal"
 import { Terminal } from "@/components/terminal"
-import { checksum, base64Decode } from "@opencode-ai/util/encode"
+import { checksum, base64Encode, base64Decode } from "@opencode-ai/util/encode"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { DialogSelectFile } from "@/components/dialog-select-file"
 import { DialogSelectFile } from "@/components/dialog-select-file"
 import { DialogSelectModel } from "@/components/dialog-select-model"
 import { DialogSelectModel } from "@/components/dialog-select-model"
@@ -250,6 +250,13 @@ export default function Page() {
     newSessionWorktree: "main",
     newSessionWorktree: "main",
   })
   })
 
 
+  const newSessionWorktree = createMemo(() => {
+    if (store.newSessionWorktree === "create") return "create"
+    const project = sync.project
+    if (project && sync.data.path.directory !== project.worktree) return sync.data.path.directory
+    return "main"
+  })
+
   const activeMessage = createMemo(() => {
   const activeMessage = createMemo(() => {
     if (!store.messageId) return lastUserMessage()
     if (!store.messageId) return lastUserMessage()
     const found = visibleUserMessages()?.find((m) => m.id === store.messageId)
     const found = visibleUserMessages()?.find((m) => m.id === store.messageId)
@@ -866,8 +873,21 @@ export default function Page() {
               </Match>
               </Match>
               <Match when={true}>
               <Match when={true}>
                 <NewSessionView
                 <NewSessionView
-                  worktree={store.newSessionWorktree}
-                  onWorktreeChange={(value) => setStore("newSessionWorktree", value)}
+                  worktree={newSessionWorktree()}
+                  onWorktreeChange={(value) => {
+                    if (value === "create") {
+                      setStore("newSessionWorktree", value)
+                      return
+                    }
+
+                    setStore("newSessionWorktree", "main")
+
+                    const target = value === "main" ? sync.project?.worktree : value
+                    if (!target) return
+                    if (target === sync.data.path.directory) return
+                    layout.projects.open(target)
+                    navigate(`/${base64Encode(target)}/session`)
+                  }}
                 />
                 />
               </Match>
               </Match>
             </Switch>
             </Switch>
@@ -885,7 +905,7 @@ export default function Page() {
                 ref={(el) => {
                 ref={(el) => {
                   inputRef = el
                   inputRef = el
                 }}
                 }}
-                newSessionWorktree={store.newSessionWorktree}
+                newSessionWorktree={newSessionWorktree()}
                 onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")}
                 onNewSessionWorktreeReset={() => setStore("newSessionWorktree", "main")}
               />
               />
             </div>
             </div>