refactor(workspace): migrate from per-folder windows to single-window workspace
Replace the legacy folder + welcome routes with a unified /workspace route that hosts all folders, conversations, tabs, and terminals in one window. - Persist opened tabs to the database (opened_tabs entity + migration) so tab layout survives restarts and deep-link bootstrap restores state - Replace FolderContext shim with AppWorkspaceProvider, ActiveFolderProvider, and TabProvider; expose both opened (folders) and full DB (allFolders) listings via list_all_folder_details - Return conversations across all non-deleted folders from list_all when no folder filter is given, so the sidebar can show every folder's history - Add ConversationContextBar above the chat input with folder picker (auto-opens unopened folders on select), branch picker, and commit / push / merge / stash entries to restore BranchDropdown functionality - Rework sidebar with stats header, search, flat / folder-grouped view modes (localStorage-persisted), reveal-in-sidebar event subscriber, and per-folder context menu (focus, close tabs, remove from workspace); indent conversations under folder headers in grouped mode - Gate terminal creation on active folder and show folder context - Remove deprecated BranchDropdown, FolderNameDropdown, welcome route, and per-folder window commands - Localize all new strings across 10 locales
This commit is contained in:
@@ -21,7 +21,7 @@ import type {
|
||||
FolderDetail,
|
||||
DbConversationSummary,
|
||||
ImportResult,
|
||||
OpenedConversation,
|
||||
OpenedTab,
|
||||
GitStatusEntry,
|
||||
GitBranchList,
|
||||
GitPullResult,
|
||||
@@ -598,22 +598,48 @@ export async function getFolder(folderId: number): Promise<FolderDetail> {
|
||||
return getTransport().call("get_folder", { folderId })
|
||||
}
|
||||
|
||||
export async function listFolderConversations(params: {
|
||||
folder_id: number
|
||||
export async function listAllConversations(params?: {
|
||||
folder_ids?: number[] | null
|
||||
agent_type?: AgentType | null
|
||||
search?: string | null
|
||||
sort_by?: string | null
|
||||
status?: string | null
|
||||
}): Promise<DbConversationSummary[]> {
|
||||
return getTransport().call("list_folder_conversations", {
|
||||
folderId: params.folder_id,
|
||||
agentType: params.agent_type ?? null,
|
||||
search: params.search ?? null,
|
||||
sortBy: params.sort_by ?? null,
|
||||
status: params.status ?? null,
|
||||
return getTransport().call("list_all_conversations", {
|
||||
folderIds: params?.folder_ids ?? null,
|
||||
agentType: params?.agent_type ?? null,
|
||||
search: params?.search ?? null,
|
||||
sortBy: params?.sort_by ?? null,
|
||||
status: params?.status ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
export async function listOpenedTabs(): Promise<OpenedTab[]> {
|
||||
return getTransport().call("list_opened_tabs")
|
||||
}
|
||||
|
||||
export async function saveOpenedTabs(items: OpenedTab[]): Promise<void> {
|
||||
return getTransport().call("save_opened_tabs", { items })
|
||||
}
|
||||
|
||||
export async function listOpenFolderDetails(): Promise<FolderDetail[]> {
|
||||
return getTransport().call("list_open_folder_details")
|
||||
}
|
||||
|
||||
export async function listAllFolderDetails(): Promise<FolderDetail[]> {
|
||||
return getTransport().call("list_all_folder_details")
|
||||
}
|
||||
|
||||
export async function openFolderById(folderId: number): Promise<FolderDetail> {
|
||||
return getTransport().call("open_folder_by_id", { folderId })
|
||||
}
|
||||
|
||||
export async function removeFolderFromWorkspace(
|
||||
folderId: number
|
||||
): Promise<void> {
|
||||
return getTransport().call("remove_folder_from_workspace", { folderId })
|
||||
}
|
||||
|
||||
export async function importLocalConversations(
|
||||
folderId: number
|
||||
): Promise<ImportResult> {
|
||||
@@ -626,16 +652,6 @@ export async function getFolderConversation(
|
||||
return getTransport().call("get_folder_conversation", { conversationId })
|
||||
}
|
||||
|
||||
export async function saveFolderOpenedConversations(
|
||||
folderId: number,
|
||||
items: OpenedConversation[]
|
||||
): Promise<void> {
|
||||
return getTransport().call("save_folder_opened_conversations", {
|
||||
folderId,
|
||||
items,
|
||||
})
|
||||
}
|
||||
|
||||
export async function setFolderParentBranch(
|
||||
path: string,
|
||||
parentBranch: string | null
|
||||
@@ -1053,23 +1069,8 @@ export async function gitAddFiles(
|
||||
|
||||
// Window management commands
|
||||
|
||||
export async function openFolderWindow(
|
||||
path: string,
|
||||
options?: { newWindow?: boolean }
|
||||
): Promise<void> {
|
||||
if (getTransport().isDesktop()) {
|
||||
return getTransport().call("open_folder_window", { path })
|
||||
}
|
||||
const entry = await getTransport().call<{ id: number }>(
|
||||
"open_folder_window",
|
||||
{ path }
|
||||
)
|
||||
const url = `/folder?id=${entry.id}`
|
||||
if (options?.newWindow) {
|
||||
window.open(url, `folder-${entry.id}`)
|
||||
} else {
|
||||
window.location.href = url
|
||||
}
|
||||
export async function openFolder(path: string): Promise<FolderDetail> {
|
||||
return getTransport().call("open_folder", { path })
|
||||
}
|
||||
|
||||
export async function openCommitWindow(folderId: number): Promise<void> {
|
||||
@@ -1120,7 +1121,9 @@ export async function openProjectBootWindow(source?: string): Promise<void> {
|
||||
if (getTransport().isDesktop()) {
|
||||
return getTransport().call("open_project_boot_window", { source })
|
||||
}
|
||||
window.open("/project-boot", "project-boot")
|
||||
if (typeof window !== "undefined") {
|
||||
window.open("/project-boot", "project-boot")
|
||||
}
|
||||
}
|
||||
|
||||
export async function detectPackageManager(
|
||||
@@ -1145,27 +1148,6 @@ export async function createShadcnProject(params: {
|
||||
})
|
||||
}
|
||||
|
||||
export async function listOpenFolders(): Promise<FolderHistoryEntry[]> {
|
||||
return getTransport().call("list_open_folders")
|
||||
}
|
||||
|
||||
export async function focusFolderWindow(folderId: number): Promise<void> {
|
||||
if (getTransport().isDesktop()) {
|
||||
return getTransport().call("focus_folder_window", { folderId })
|
||||
}
|
||||
// Web mode: open empty string to focus existing named window without reload.
|
||||
// If the window doesn't exist (was closed), open the folder page.
|
||||
const win = window.open("", `folder-${folderId}`)
|
||||
if (
|
||||
!win ||
|
||||
win.closed ||
|
||||
!win.location.href ||
|
||||
win.location.href === "about:blank"
|
||||
) {
|
||||
window.open(`/folder?id=${folderId}`, `folder-${folderId}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Conversation CRUD commands
|
||||
|
||||
export async function createConversation(
|
||||
|
||||
Reference in New Issue
Block a user