Update workspace and app changes

This commit is contained in:
Mohamed Boudra
2026-03-07 19:09:00 +07:00
parent 1d1c7058f1
commit 7d76da3249
40 changed files with 2693 additions and 976 deletions
+4 -4
View File
@@ -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?: {