Update workspace and app changes
This commit is contained in:
@@ -9,7 +9,7 @@ import {
|
||||
takeCommandCenterFocusRestoreElement,
|
||||
} from "@/utils/command-center-focus-restore";
|
||||
import {
|
||||
buildHostNewAgentRoute,
|
||||
buildHostOpenProjectRoute,
|
||||
buildHostWorkspaceAgentRoute,
|
||||
buildHostSettingsRoute,
|
||||
parseHostAgentRouteFromPathname,
|
||||
@@ -55,10 +55,10 @@ type CommandCenterActionDefinition = {
|
||||
const COMMAND_CENTER_ACTIONS: readonly CommandCenterActionDefinition[] = [
|
||||
{
|
||||
id: "new-agent",
|
||||
title: "New agent",
|
||||
title: "Open project",
|
||||
icon: "plus",
|
||||
shortcutKeys: ["mod", "shift", "O"],
|
||||
keywords: ["new", "new agent", "create", "start", "launch", "agent"],
|
||||
keywords: ["open", "project", "folder", "workspace", "repo"],
|
||||
buildRoute: ({ newAgentRoute }) => newAgentRoute,
|
||||
},
|
||||
{
|
||||
@@ -123,7 +123,7 @@ export function useCommandCenter() {
|
||||
const newAgentRoute = useMemo<Href>(() => {
|
||||
const serverIdFromPath =
|
||||
parseServerIdFromPathname(pathname) ?? fallbackServerId;
|
||||
return serverIdFromPath ? (buildHostNewAgentRoute(serverIdFromPath) as Href) : "/";
|
||||
return serverIdFromPath ? (buildHostOpenProjectRoute(serverIdFromPath) as Href) : "/";
|
||||
}, [fallbackServerId, pathname]);
|
||||
|
||||
const settingsRoute = useMemo<Href>(() => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useSessionStore } from "@/stores/session-store";
|
||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||
import { setCommandCenterFocusRestoreElement } from "@/utils/command-center-focus-restore";
|
||||
import {
|
||||
buildHostNewAgentRoute,
|
||||
buildHostOpenProjectRoute,
|
||||
buildHostWorkspaceRoute,
|
||||
parseHostAgentRouteFromPathname,
|
||||
parseHostWorkspaceRouteFromPathname,
|
||||
@@ -99,7 +99,7 @@ export function useKeyboardShortcuts({
|
||||
return true;
|
||||
};
|
||||
|
||||
const navigateToNewAgent = (): boolean => {
|
||||
const navigateToOpenProject = (): boolean => {
|
||||
let targetServerId = parseServerIdFromPathname(pathname);
|
||||
|
||||
if (!targetServerId) {
|
||||
@@ -111,7 +111,7 @@ export function useKeyboardShortcuts({
|
||||
return false;
|
||||
}
|
||||
|
||||
router.push(buildHostNewAgentRoute(targetServerId) as any);
|
||||
router.push(buildHostOpenProjectRoute(targetServerId) as any);
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -171,7 +171,7 @@ export function useKeyboardShortcuts({
|
||||
}): boolean => {
|
||||
switch (input.action) {
|
||||
case "agent.new":
|
||||
return navigateToNewAgent();
|
||||
return navigateToOpenProject();
|
||||
case "workspace.tab.new":
|
||||
return requestWorkspaceTabAction({ kind: "new" });
|
||||
case "workspace.tab.close.current":
|
||||
|
||||
@@ -14,6 +14,28 @@ function item(key: string): OrderedItem {
|
||||
return { key }
|
||||
}
|
||||
|
||||
function workspace(
|
||||
input: Pick<WorkspaceDescriptor, "id" | "projectId" | "name" | "status" | "activityAt"> &
|
||||
Partial<
|
||||
Pick<
|
||||
WorkspaceDescriptor,
|
||||
"projectDisplayName" | "projectRootPath" | "projectKind" | "workspaceKind"
|
||||
>
|
||||
>
|
||||
): WorkspaceDescriptor {
|
||||
return {
|
||||
id: input.id,
|
||||
projectId: input.projectId,
|
||||
projectDisplayName: input.projectDisplayName ?? input.projectId,
|
||||
projectRootPath: input.projectRootPath ?? input.id,
|
||||
projectKind: input.projectKind ?? "git",
|
||||
workspaceKind: input.workspaceKind ?? "local_checkout",
|
||||
name: input.name,
|
||||
status: input.status,
|
||||
activityAt: input.activityAt,
|
||||
}
|
||||
}
|
||||
|
||||
describe('applyStoredOrdering', () => {
|
||||
it('keeps unknown items on the baseline while applying stored order', () => {
|
||||
const result = applyStoredOrdering({
|
||||
@@ -72,13 +94,13 @@ describe('appendMissingOrderKeys', () => {
|
||||
describe('buildSidebarProjectsFromWorkspaces', () => {
|
||||
it('uses workspace descriptor name and status directly', () => {
|
||||
const workspaces: WorkspaceDescriptor[] = [
|
||||
{
|
||||
workspace({
|
||||
id: '/repo/main',
|
||||
projectId: 'project-1',
|
||||
name: 'feat/hard-cut',
|
||||
status: 'failed',
|
||||
activityAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const projects = buildSidebarProjectsFromWorkspaces({
|
||||
@@ -96,20 +118,20 @@ describe('buildSidebarProjectsFromWorkspaces', () => {
|
||||
|
||||
it('preserves stored project order even when activity changes', () => {
|
||||
const initialWorkspaces: WorkspaceDescriptor[] = [
|
||||
{
|
||||
workspace({
|
||||
id: '/repo/b',
|
||||
projectId: 'project-b',
|
||||
name: 'feat/b',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-02T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
}),
|
||||
workspace({
|
||||
id: '/repo/a',
|
||||
projectId: 'project-a',
|
||||
name: 'feat/a',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
},
|
||||
}),
|
||||
]
|
||||
|
||||
const seededOrder = appendMissingOrderKeys({
|
||||
@@ -125,20 +147,20 @@ describe('buildSidebarProjectsFromWorkspaces', () => {
|
||||
const updatedProjects = buildSidebarProjectsFromWorkspaces({
|
||||
serverId: 'srv',
|
||||
workspaces: [
|
||||
{
|
||||
workspace({
|
||||
id: '/repo/b',
|
||||
projectId: 'project-b',
|
||||
name: 'feat/b',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-02T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
}),
|
||||
workspace({
|
||||
id: '/repo/a',
|
||||
projectId: 'project-a',
|
||||
name: 'feat/a',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-03T00:00:00.000Z'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
projectOrder: seededOrder,
|
||||
workspaceOrderByScope: {},
|
||||
@@ -151,27 +173,27 @@ describe('buildSidebarProjectsFromWorkspaces', () => {
|
||||
const projects = buildSidebarProjectsFromWorkspaces({
|
||||
serverId: 'srv',
|
||||
workspaces: [
|
||||
{
|
||||
workspace({
|
||||
id: '/repo/c',
|
||||
projectId: 'project-c',
|
||||
name: 'feat/c',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-04T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
}),
|
||||
workspace({
|
||||
id: '/repo/b',
|
||||
projectId: 'project-b',
|
||||
name: 'feat/b',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-02T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
}),
|
||||
workspace({
|
||||
id: '/repo/a',
|
||||
projectId: 'project-a',
|
||||
name: 'feat/a',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
projectOrder: ['project-b', 'project-a', 'project-c'],
|
||||
workspaceOrderByScope: {},
|
||||
@@ -184,20 +206,20 @@ describe('buildSidebarProjectsFromWorkspaces', () => {
|
||||
const initialProjects = buildSidebarProjectsFromWorkspaces({
|
||||
serverId: 'srv',
|
||||
workspaces: [
|
||||
{
|
||||
workspace({
|
||||
id: '/repo/main',
|
||||
projectId: 'project-1',
|
||||
name: 'main',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-02T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
}),
|
||||
workspace({
|
||||
id: '/repo/feature',
|
||||
projectId: 'project-1',
|
||||
name: 'feature',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-01T00:00:00.000Z'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
projectOrder: ['project-1'],
|
||||
workspaceOrderByScope: {},
|
||||
@@ -211,20 +233,20 @@ describe('buildSidebarProjectsFromWorkspaces', () => {
|
||||
const projects = buildSidebarProjectsFromWorkspaces({
|
||||
serverId: 'srv',
|
||||
workspaces: [
|
||||
{
|
||||
workspace({
|
||||
id: '/repo/main',
|
||||
projectId: 'project-1',
|
||||
name: 'main',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-02T00:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
}),
|
||||
workspace({
|
||||
id: '/repo/feature',
|
||||
projectId: 'project-1',
|
||||
name: 'feature',
|
||||
status: 'running',
|
||||
activityAt: new Date('2026-01-03T00:00:00.000Z'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
projectOrder: ['project-1'],
|
||||
workspaceOrderByScope: {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useCallback, useEffect, useMemo, useSyncExternalStore } from 'react'
|
||||
import { useSessionStore } from '@/stores/session-store'
|
||||
import { normalizeWorkspaceDescriptor, useSessionStore } from '@/stores/session-store'
|
||||
import { getHostRuntimeStore } from '@/runtime/host-runtime'
|
||||
import { useSidebarOrderStore } from '@/stores/sidebar-order-store'
|
||||
import type { WorkspaceDescriptor } from '@/stores/session-store'
|
||||
import { projectDisplayNameFromProjectId } from '@/utils/project-display-name'
|
||||
import { normalizeWorkspaceIdentity } from '@/utils/workspace-identity'
|
||||
|
||||
const EMPTY_ORDER: string[] = []
|
||||
const EMPTY_PROJECTS: SidebarProjectEntry[] = []
|
||||
@@ -15,6 +14,7 @@ export interface SidebarWorkspaceEntry {
|
||||
workspaceKey: string
|
||||
serverId: string
|
||||
workspaceId: string
|
||||
workspaceKind: WorkspaceDescriptor['workspaceKind']
|
||||
name: string
|
||||
activityAt: Date | null
|
||||
statusBucket: SidebarStateBucket
|
||||
@@ -23,6 +23,7 @@ export interface SidebarWorkspaceEntry {
|
||||
export interface SidebarProjectEntry {
|
||||
projectKey: string
|
||||
projectName: string
|
||||
projectKind: WorkspaceDescriptor['projectKind']
|
||||
iconWorkingDir: string
|
||||
statusBucket: SidebarStateBucket
|
||||
activeCount: number
|
||||
@@ -110,8 +111,9 @@ export function buildSidebarProjectsFromWorkspaces(input: {
|
||||
byProject.get(workspace.projectId) ??
|
||||
({
|
||||
projectKey: workspace.projectId,
|
||||
projectName: projectDisplayNameFromProjectId(workspace.projectId),
|
||||
iconWorkingDir: workspace.id,
|
||||
projectName: workspace.projectDisplayName || projectDisplayNameFromProjectId(workspace.projectId),
|
||||
projectKind: workspace.projectKind,
|
||||
iconWorkingDir: workspace.projectRootPath || workspace.id,
|
||||
statusBucket: 'done',
|
||||
activeCount: 0,
|
||||
totalWorkspaces: 0,
|
||||
@@ -123,6 +125,7 @@ export function buildSidebarProjectsFromWorkspaces(input: {
|
||||
workspaceKey: `${input.serverId}:${workspace.id}`,
|
||||
serverId: input.serverId,
|
||||
workspaceId: workspace.id,
|
||||
workspaceKind: workspace.workspaceKind,
|
||||
name: workspace.name,
|
||||
activityAt: workspace.activityAt,
|
||||
statusBucket: workspace.status,
|
||||
@@ -241,18 +244,15 @@ function getWorkspaceOrderScopeKey(serverId: string, projectKey: string): string
|
||||
function toWorkspaceDescriptor(payload: {
|
||||
id: string
|
||||
projectId: string
|
||||
projectDisplayName: string
|
||||
projectRootPath: string
|
||||
projectKind: WorkspaceDescriptor['projectKind']
|
||||
workspaceKind: WorkspaceDescriptor['workspaceKind']
|
||||
name: string
|
||||
status: WorkspaceDescriptor['status']
|
||||
activityAt: string | null
|
||||
}): WorkspaceDescriptor {
|
||||
const activityAt = payload.activityAt ? new Date(payload.activityAt) : null
|
||||
return {
|
||||
id: normalizeWorkspaceIdentity(payload.id) ?? payload.id,
|
||||
projectId: payload.projectId,
|
||||
name: payload.name,
|
||||
status: payload.status,
|
||||
activityAt: activityAt && !Number.isNaN(activityAt.getTime()) ? activityAt : null,
|
||||
}
|
||||
return normalizeWorkspaceDescriptor(payload)
|
||||
}
|
||||
|
||||
export function useSidebarWorkspacesList(options?: {
|
||||
|
||||
Reference in New Issue
Block a user