summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components/session
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-01-02 20:17:02 -0600
committerGitHub <[email protected]>2026-01-02 20:17:02 -0600
commit052de3c556097a983c7c27e9a9a9cbc5d22b13c5 (patch)
tree6b2f8d7d74e1027a110f8ee17ab9fd538c25ccfd /packages/app/src/components/session
parentf6fe709f6ee75427ba64829af25b64d9a3111569 (diff)
downloadopencode-052de3c556097a983c7c27e9a9a9cbc5d22b13c5.tar.gz
opencode-052de3c556097a983c7c27e9a9a9cbc5d22b13c5.zip
feat: add managed git worktrees (#6674)
Diffstat (limited to 'packages/app/src/components/session')
-rw-r--r--packages/app/src/components/session/session-header.tsx17
-rw-r--r--packages/app/src/components/session/session-new-view.tsx48
2 files changed, 53 insertions, 12 deletions
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index 0a0b53607..7e4ade523 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -1,4 +1,4 @@
-import { createMemo, createResource, Show } from "solid-js"
+import { createEffect, createMemo, createResource, Show } from "solid-js"
import { A, useNavigate, useParams } from "@solidjs/router"
import { useLayout } from "@/context/layout"
import { useCommand } from "@/context/command"
@@ -7,7 +7,7 @@ import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useSync } from "@/context/sync"
import { useGlobalSDK } from "@/context/global-sdk"
import { getFilename } from "@opencode-ai/util/path"
-import { base64Encode } from "@opencode-ai/util/encode"
+import { base64Decode, base64Encode } from "@opencode-ai/util/encode"
import { iife } from "@opencode-ai/util/iife"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -31,10 +31,11 @@ export function SessionHeader() {
const dialog = useDialog()
const sync = useSync()
+ const projectDirectory = createMemo(() => base64Decode(params.dir ?? ""))
+
const sessions = createMemo(() => (sync.data.session ?? []).filter((s) => !s.parentID))
const currentSession = createMemo(() => sessions().find((s) => s.id === params.id))
const shareEnabled = createMemo(() => sync.data.config.share !== "disabled")
- const branch = createMemo(() => sync.data.vcs?.branch)
function navigateToProject(directory: string) {
navigate(`/${base64Encode(directory)}`)
@@ -60,12 +61,8 @@ export function SessionHeader() {
<div class="hidden xl:flex items-center gap-2">
<Select
options={layout.projects.list().map((project) => project.worktree)}
- current={sync.directory}
- label={(x) => {
- const name = getFilename(x)
- const b = x === sync.directory ? branch() : undefined
- return b ? `${name}:${b}` : name
- }}
+ current={sync.project?.worktree ?? projectDirectory()}
+ label={(x) => getFilename(x)}
onSelect={(x) => (x ? navigateToProject(x) : undefined)}
class="text-14-regular text-text-base"
variant="ghost"
@@ -191,7 +188,7 @@ export function SessionHeader() {
let shareURL = session.share?.url
if (!shareURL) {
shareURL = await globalSDK.client.session
- .share({ sessionID: session.id, directory: sync.directory })
+ .share({ sessionID: session.id, directory: projectDirectory() })
.then((r) => r.data?.share?.url)
.catch((e) => {
console.error("Failed to share session", e)
diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx
index 90a280612..bd8033ae6 100644
--- a/packages/app/src/components/session/session-new-view.tsx
+++ b/packages/app/src/components/session/session-new-view.tsx
@@ -1,12 +1,41 @@
-import { Show } from "solid-js"
+import { Show, createMemo } from "solid-js"
import { DateTime } from "luxon"
import { useSync } from "@/context/sync"
import { Icon } from "@opencode-ai/ui/icon"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { Select } from "@opencode-ai/ui/select"
-export function NewSessionView() {
+const MAIN_WORKTREE = "main"
+const CREATE_WORKTREE = "create"
+
+interface NewSessionViewProps {
+ worktree: string
+ onWorktreeChange: (value: string) => void
+}
+
+export function NewSessionView(props: NewSessionViewProps) {
const sync = useSync()
+ const sandboxes = createMemo(() => sync.project?.sandboxes ?? [])
+ const options = createMemo(() => [MAIN_WORKTREE, ...sandboxes(), CREATE_WORKTREE])
+ const current = createMemo(() => {
+ const selection = props.worktree
+ if (options().includes(selection)) return selection
+ return MAIN_WORKTREE
+ })
+
+ const label = (value: string) => {
+ if (value === MAIN_WORKTREE) {
+ const branch = sync.data.vcs?.branch
+ if (branch) return `Current branch (${branch})`
+ return "Main branch"
+ }
+
+ if (value === CREATE_WORKTREE) return "Create new worktree"
+
+ return getFilename(value)
+ }
+
return (
<div class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6">
<div class="text-20-medium text-text-weaker">New session</div>
@@ -17,6 +46,21 @@ export function NewSessionView() {
<span class="text-text-strong">{getFilename(sync.data.path.directory)}</span>
</div>
</div>
+ <div class="flex justify-center items-center gap-1">
+ <Icon name="branch" size="small" />
+ <Select
+ options={options()}
+ current={current()}
+ value={(x) => x}
+ label={label}
+ onSelect={(value) => {
+ props.onWorktreeChange(value ?? MAIN_WORKTREE)
+ }}
+ size="normal"
+ variant="ghost"
+ class="text-12-medium"
+ />
+ </div>
<Show when={sync.project}>
{(project) => (
<div class="flex justify-center items-center gap-3">