Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/app/src/components/session/session-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,12 @@ export function SessionHeader() {
return layout.projects.list().find((p) => p.worktree === directory || p.sandboxes?.includes(directory))
})
const name = createMemo(() => {
const dir = projectDirectory()
const current = project()
// When in a workspace (sandbox), show the workspace name, not the project root
if (current && dir && dir !== current.worktree) return getFilename(dir)
if (current) return current.name || getFilename(current.worktree)
return getFilename(projectDirectory())
return getFilename(dir)
})
const hotkey = createMemo(() => command.keybind("file.open"))
const os = createMemo(() => detectOS(platform))
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/components/session/session-new-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function NewSessionView(props: NewSessionViewProps) {
if (options().includes(selection)) return selection
return MAIN_WORKTREE
})
const projectRoot = createMemo(() => sync.project?.worktree ?? sdk.directory)
const displayDir = createMemo(() => sdk.directory)
const isWorktree = createMemo(() => {
const project = sync.project
if (!project) return false
Expand Down Expand Up @@ -59,8 +59,8 @@ export function NewSessionView(props: NewSessionViewProps) {
<div class="w-full flex flex-col gap-4 items-center">
<div class="flex items-start justify-center gap-3 min-h-5">
<div class="text-12-medium text-text-weak select-text leading-5 min-w-0 max-w-160 break-words text-center">
{getDirectory(projectRoot())}
<span class="text-text-strong">{getFilename(projectRoot())}</span>
{getDirectory(displayDir())}
<span class="text-text-strong">{getFilename(displayDir())}</span>
</div>
</div>
<div class="flex items-start justify-center gap-1.5 min-h-5">
Expand Down
56 changes: 54 additions & 2 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ import {
effectiveWorkspaceOrder,
errorMessage,
latestRootSession,
sortSessions,
sortedRootSessions,
workspaceKey,
} from "./layout/helpers"
Expand Down Expand Up @@ -572,6 +573,17 @@ export default function Layout(props: ParentProps) {
return projects.find((p) => p.worktree === root)
})

// Auto-enable workspaces when the current directory is a sandbox of a project
createEffect(() => {
const dir = currentDir()
const project = currentProject()
if (!dir || !project) return
if (dir === project.worktree) return
if (project.vcs !== "git") return
if (layout.sidebar.workspaces(project.worktree)()) return
layout.sidebar.setWorkspaces(project.worktree, true)
})

createEffect(
on(
() => ({ ready: pageReady(), layoutReady: layoutReady(), dir: params.dir, list: layout.projects.list() }),
Expand Down Expand Up @@ -620,8 +632,11 @@ export default function Layout(props: ParentProps) {
setStore("workspaceBranchName", projectId, branch, next)
}

const workspaceLabel = (directory: string, branch?: string, projectId?: string) =>
workspaceName(directory, projectId, branch) ?? branch ?? getFilename(directory)
const workspaceLabel = (directory: string, branch?: string, projectId?: string) => {
// For JJ co-located workspaces, branch is "HEAD" (detached) — fall through to directory name
const effectiveBranch = branch && branch !== "HEAD" ? branch : undefined
return workspaceName(directory, projectId, effectiveBranch) ?? effectiveBranch ?? getFilename(directory)
}

const workspaceSetting = createMemo(() => {
const project = currentProject()
Expand Down Expand Up @@ -1234,6 +1249,12 @@ export default function Layout(props: ParentProps) {
const root = projectRoot(directory)
server.projects.touch(root)
const project = layout.projects.list().find((item) => item.worktree === root)
// Auto-enable workspaces when navigating to a sandbox directory
if (project && directory !== root && project.vcs === "git") {
if (!layout.sidebar.workspaces(root)()) {
layout.sidebar.setWorkspaces(root, true)
}
}
let dirs = project
? effectiveWorkspaceOrder(root, [root, ...(project.sandboxes ?? [])], store.workspaceOrder[root])
: [root]
Expand Down Expand Up @@ -1269,6 +1290,37 @@ export default function Layout(props: ParentProps) {
return true
}

// When user explicitly opened a specific workspace (not the project root),
// navigate directly to that workspace instead of searching across all workspaces.
if (workspaceKey(directory) !== workspaceKey(root)) {
await refreshDirs(directory)
if (canOpen(directory)) {
const [dirStore] = globalSync.child(directory, { bootstrap: false })
const latest = sortedRootSessions(dirStore, Date.now())[0]
if (latest) {
setStore("lastProjectSession", root, { directory, id: latest.id, at: Date.now() })
navigateWithSidebarReset(`/${base64Encode(directory)}/session/${latest.id}`)
return
}
// Try fetching sessions from server for this specific workspace
const fetched = await globalSDK.client.session
.list({ directory })
.then((x) => x.data ?? [])
.catch(() => [] as Session[])
const visible = fetched
.filter((s) => !s.parentID && !s.time?.archived)
.sort(sortSessions(Date.now()))
if (visible[0]) {
setStore("lastProjectSession", root, { directory, id: visible[0].id, at: Date.now() })
navigateWithSidebarReset(`/${base64Encode(directory)}/session/${visible[0].id}`)
return
}
}
// No sessions in this workspace — navigate to it anyway (empty state)
navigateWithSidebarReset(`/${base64Encode(directory)}/session`)
return
}

const projectSession = store.lastProjectSession[root]
if (projectSession?.id) {
await refreshDirs(projectSession.directory)
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/pages/layout/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const workspaceKey = (directory: string) => {
return directory.replace(/[\\/]+$/, "")
}

function sortSessions(now: number) {
export function sortSessions(now: number) {
const oneMinuteAgo = now - 60 * 1000
return (a: Session, b: Session) => {
const aUpdated = a.time.updated ?? a.time.created
Expand Down
7 changes: 4 additions & 3 deletions packages/app/src/pages/layout/sidebar-workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ const WorkspaceHeader = (props: {
when={!props.local()}
fallback={
<span class="text-14-medium text-text-base min-w-0 truncate">
{props.branch() ?? getFilename(props.directory)}
{props.branch() && props.branch() !== "HEAD" ? props.branch() : getFilename(props.directory)}
</span>
}
>
Expand Down Expand Up @@ -326,8 +326,9 @@ export const SortableWorkspace = (props: {
const active = createMemo(() => props.ctx.currentDir() === props.directory)
const workspaceValue = createMemo(() => {
const branch = workspaceStore.vcs?.branch
const name = branch ?? getFilename(props.directory)
return props.ctx.workspaceName(props.directory, props.project.id, branch) ?? name
const effectiveBranch = branch && branch !== "HEAD" ? branch : undefined
const name = effectiveBranch ?? getFilename(props.directory)
return props.ctx.workspaceName(props.directory, props.project.id, effectiveBranch) ?? name
})
const open = createMemo(() => props.ctx.workspaceExpanded(props.directory, local()))
const boot = createMemo(() => open() || active())
Expand Down
Loading