Update workspace and app changes
This commit is contained in:
@@ -68,6 +68,12 @@ describe("workspace route parsing", () => {
|
||||
|
||||
it("builds host new-agent routes", () => {
|
||||
expect(buildHostNewAgentRoute("local")).toBe("/h/local/new-agent");
|
||||
expect(
|
||||
buildHostNewAgentRoute("local", {
|
||||
workingDir: "/tmp/repo",
|
||||
worktreeMode: "create",
|
||||
})
|
||||
).toBe("/h/local/new-agent?workingDir=%2Ftmp%2Frepo&worktreeMode=create");
|
||||
});
|
||||
|
||||
it("builds workspace routes with open intent query", () => {
|
||||
|
||||
@@ -399,12 +399,46 @@ export function buildHostAgentsRoute(serverId: string): string {
|
||||
return `${base}/agents`;
|
||||
}
|
||||
|
||||
export function buildHostNewAgentRoute(serverId: string): string {
|
||||
export function buildHostNewAgentRoute(
|
||||
serverId: string,
|
||||
options?: {
|
||||
workingDir?: string;
|
||||
provider?: string;
|
||||
modeId?: string;
|
||||
model?: string;
|
||||
thinkingOptionId?: string;
|
||||
worktreeMode?: "create" | "attach";
|
||||
}
|
||||
): string {
|
||||
const base = buildHostRootRoute(serverId);
|
||||
if (base === "/") {
|
||||
return "/";
|
||||
}
|
||||
return `${base}/new-agent`;
|
||||
const searchParams = new URLSearchParams();
|
||||
const entries: Array<[string, string | undefined]> = [
|
||||
["workingDir", options?.workingDir],
|
||||
["provider", options?.provider],
|
||||
["modeId", options?.modeId],
|
||||
["model", options?.model],
|
||||
["thinkingOptionId", options?.thinkingOptionId],
|
||||
["worktreeMode", options?.worktreeMode],
|
||||
];
|
||||
for (const [key, value] of entries) {
|
||||
const normalized = trimNonEmpty(value);
|
||||
if (normalized) {
|
||||
searchParams.set(key, normalized);
|
||||
}
|
||||
}
|
||||
const search = searchParams.toString();
|
||||
return search ? `${base}/new-agent?${search}` : `${base}/new-agent`;
|
||||
}
|
||||
|
||||
export function buildHostOpenProjectRoute(serverId: string): string {
|
||||
const base = buildHostRootRoute(serverId);
|
||||
if (base === "/") {
|
||||
return "/";
|
||||
}
|
||||
return `${base}/open-project`;
|
||||
}
|
||||
|
||||
export function buildHostSettingsRoute(serverId: string): string {
|
||||
@@ -435,6 +469,9 @@ export function mapPathnameToServer(
|
||||
if (suffix.startsWith("new-agent")) {
|
||||
return `${base}/new-agent`;
|
||||
}
|
||||
if (suffix.startsWith("open-project")) {
|
||||
return `${base}/open-project`;
|
||||
}
|
||||
const workspaceRoute = parseHostWorkspaceRouteFromPathname(pathname);
|
||||
if (workspaceRoute) {
|
||||
const workspacePath = buildHostWorkspaceRoute(
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { buildSidebarProjectRowModel } from './sidebar-project-row-model'
|
||||
import type {
|
||||
SidebarProjectEntry,
|
||||
SidebarWorkspaceEntry,
|
||||
} from '@/hooks/use-sidebar-workspaces-list'
|
||||
|
||||
function workspace(overrides: Partial<SidebarWorkspaceEntry> = {}): SidebarWorkspaceEntry {
|
||||
return {
|
||||
workspaceKey: 'srv:/repo',
|
||||
serverId: 'srv',
|
||||
workspaceId: '/repo',
|
||||
workspaceKind: 'directory',
|
||||
name: 'paseo',
|
||||
activityAt: null,
|
||||
statusBucket: 'done',
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function project(overrides: Partial<SidebarProjectEntry> = {}): SidebarProjectEntry {
|
||||
return {
|
||||
projectKey: 'project-1',
|
||||
projectName: 'paseo',
|
||||
projectKind: 'git',
|
||||
iconWorkingDir: '/repo',
|
||||
statusBucket: 'done',
|
||||
activeCount: 0,
|
||||
totalWorkspaces: 1,
|
||||
latestActivityAt: null,
|
||||
workspaces: [workspace()],
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
describe('buildSidebarProjectRowModel', () => {
|
||||
it('flattens non-git projects with one workspace into a direct workspace row model', () => {
|
||||
const flattenedWorkspace = workspace({
|
||||
workspaceId: '/repo/non-git',
|
||||
workspaceKind: 'directory',
|
||||
statusBucket: 'running',
|
||||
})
|
||||
|
||||
const result = buildSidebarProjectRowModel({
|
||||
project: project({
|
||||
projectKind: 'non_git',
|
||||
workspaces: [flattenedWorkspace],
|
||||
}),
|
||||
collapsed: false,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
interaction: 'navigate',
|
||||
chevron: 'disclosure',
|
||||
trailingAction: 'none',
|
||||
flattenedWorkspace,
|
||||
selected: false,
|
||||
})
|
||||
})
|
||||
|
||||
it('marks flattened non-git project rows as selected when their workspace is active', () => {
|
||||
const flattenedWorkspace = workspace({
|
||||
serverId: 'srv-2',
|
||||
workspaceId: '/repo/non-git',
|
||||
})
|
||||
|
||||
const result = buildSidebarProjectRowModel({
|
||||
project: project({
|
||||
projectKind: 'non_git',
|
||||
workspaces: [flattenedWorkspace],
|
||||
}),
|
||||
collapsed: false,
|
||||
serverId: 'srv-2',
|
||||
activeWorkspaceSelection: {
|
||||
serverId: 'srv-2',
|
||||
workspaceId: '/repo/non-git',
|
||||
},
|
||||
})
|
||||
|
||||
expect(result.selected).toBe(true)
|
||||
})
|
||||
|
||||
it('keeps git projects as expandable sections with a new worktree action', () => {
|
||||
const result = buildSidebarProjectRowModel({
|
||||
project: project({
|
||||
projectKind: 'git',
|
||||
workspaces: [
|
||||
workspace({ workspaceId: '/repo/main', workspaceKind: 'local_checkout' }),
|
||||
workspace({ workspaceId: '/repo/feature', workspaceKind: 'worktree' }),
|
||||
],
|
||||
}),
|
||||
collapsed: true,
|
||||
})
|
||||
|
||||
expect(result).toEqual({
|
||||
interaction: 'toggle',
|
||||
chevron: 'expand',
|
||||
trailingAction: 'new_worktree',
|
||||
flattenedWorkspace: null,
|
||||
selected: false,
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,47 @@
|
||||
import type {
|
||||
SidebarProjectEntry,
|
||||
SidebarWorkspaceEntry,
|
||||
} from '@/hooks/use-sidebar-workspaces-list'
|
||||
|
||||
export interface SidebarProjectRowModel {
|
||||
interaction: 'toggle' | 'navigate'
|
||||
chevron: 'expand' | 'collapse' | 'disclosure'
|
||||
trailingAction: 'new_worktree' | 'none'
|
||||
flattenedWorkspace: SidebarWorkspaceEntry | null
|
||||
selected: boolean
|
||||
}
|
||||
|
||||
export function buildSidebarProjectRowModel(input: {
|
||||
project: SidebarProjectEntry
|
||||
collapsed: boolean
|
||||
serverId?: string | null
|
||||
activeWorkspaceSelection?: { serverId: string; workspaceId: string } | null
|
||||
}): SidebarProjectRowModel {
|
||||
const flattenedWorkspace =
|
||||
input.project.projectKind === 'non_git' && input.project.workspaces.length === 1
|
||||
? input.project.workspaces[0] ?? null
|
||||
: null
|
||||
const selected =
|
||||
flattenedWorkspace !== null &&
|
||||
Boolean(input.serverId) &&
|
||||
input.activeWorkspaceSelection?.serverId === input.serverId &&
|
||||
input.activeWorkspaceSelection?.workspaceId === flattenedWorkspace.workspaceId
|
||||
|
||||
if (flattenedWorkspace) {
|
||||
return {
|
||||
interaction: 'navigate',
|
||||
chevron: 'disclosure',
|
||||
trailingAction: 'none',
|
||||
flattenedWorkspace,
|
||||
selected,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
interaction: 'toggle',
|
||||
chevron: input.collapsed ? 'expand' : 'collapse',
|
||||
trailingAction: input.project.projectKind === 'git' ? 'new_worktree' : 'none',
|
||||
flattenedWorkspace: null,
|
||||
selected: false,
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ function workspace(serverId: string, cwd: string): SidebarWorkspaceEntry {
|
||||
workspaceKey: `${serverId}:${cwd}`,
|
||||
serverId,
|
||||
workspaceId: cwd,
|
||||
workspaceKind: "local_checkout",
|
||||
name: cwd,
|
||||
activityAt: null,
|
||||
statusBucket: "done",
|
||||
@@ -18,6 +19,7 @@ function project(projectKey: string, workspaces: SidebarWorkspaceEntry[]): Sideb
|
||||
return {
|
||||
projectKey,
|
||||
projectName: projectKey,
|
||||
projectKind: "git",
|
||||
iconWorkingDir: workspaces[0]?.workspaceId ?? "",
|
||||
statusBucket: "done",
|
||||
activeCount: 0,
|
||||
|
||||
Reference in New Issue
Block a user