Switch to double quotes and reformat codebase with Biome

This commit is contained in:
Mohamed Boudra
2026-03-21 20:22:15 +07:00
parent ab40786882
commit f6579dbeb4
805 changed files with 38132 additions and 40175 deletions
@@ -51,9 +51,7 @@ describe("checkout-git-actions-store", () => {
const second = store.commit({ serverId, cwd });
expect(client.checkoutCommit).toHaveBeenCalledTimes(1);
expect(
store.getStatus({ serverId, cwd, actionId: "commit" })
).toBe("pending");
expect(store.getStatus({ serverId, cwd, actionId: "commit" })).toBe("pending");
deferred.resolve({});
await Promise.all([first, second]);
@@ -64,4 +62,3 @@ describe("checkout-git-actions-store", () => {
expect(store.getStatus({ serverId, cwd, actionId: "commit" })).toBe("idle");
});
});
@@ -33,7 +33,7 @@ function resolveClient(serverId: string) {
function setStatus(
key: CheckoutKey,
actionId: CheckoutGitAsyncActionId,
status: CheckoutGitActionStatus
status: CheckoutGitActionStatus,
) {
useCheckoutGitActionsStore.setState((state) => {
const current = state.statusByCheckout[key]?.[actionId] ?? "idle";
@@ -61,10 +61,7 @@ function invalidateCheckoutGitQueries(serverId: string, cwd: string) {
predicate: (query) => {
const key = query.queryKey;
return (
Array.isArray(key) &&
key[0] === "checkoutDiff" &&
key[1] === serverId &&
key[2] === cwd
Array.isArray(key) && key[0] === "checkoutDiff" && key[1] === serverId && key[2] === cwd
);
},
});
@@ -72,10 +69,7 @@ function invalidateCheckoutGitQueries(serverId: string, cwd: string) {
predicate: (query) => {
const key = query.queryKey;
return (
Array.isArray(key) &&
key[0] === "checkoutPrStatus" &&
key[1] === serverId &&
key[2] === cwd
Array.isArray(key) && key[0] === "checkoutPrStatus" && key[1] === serverId && key[2] === cwd
);
},
});
@@ -92,10 +86,7 @@ function invalidateWorktreeList() {
});
}
function removeWorktreeFromCachedLists(input: {
serverId: string;
worktreePath: string;
}): void {
function removeWorktreeFromCachedLists(input: { serverId: string; worktreePath: string }): void {
const serverId = input.serverId.trim();
const worktreePath = input.worktreePath.trim();
if (!serverId || !worktreePath) {
@@ -117,7 +108,7 @@ function removeWorktreeFromCachedLists(input: {
query.queryKey[0] === "paseoWorktreeList" &&
query.queryKey[1] === serverId,
},
removeFromList
removeFromList,
);
queryClient.setQueriesData(
@@ -127,7 +118,7 @@ function removeWorktreeFromCachedLists(input: {
query.queryKey[0] === "sidebarPaseoWorktreeList" &&
query.queryKey[1] === serverId,
},
removeFromList
removeFromList,
);
}
+2 -7
View File
@@ -16,14 +16,9 @@ type PendingCreateAttempt = {
type CreateFlowState = {
pendingByDraftId: Record<string, PendingCreateAttempt>;
setPending: (
pending: Omit<PendingCreateAttempt, "lifecycle">
) => void;
setPending: (pending: Omit<PendingCreateAttempt, "lifecycle">) => void;
updateAgentId: (input: { draftId: string; agentId: string }) => void;
markLifecycle: (input: {
draftId: string;
lifecycle: CreateFlowLifecycleState;
}) => void;
markLifecycle: (input: { draftId: string; lifecycle: CreateFlowLifecycleState }) => void;
rekeyDraft: (input: { fromDraftId: string; toDraftId: string }) => void;
clear: (input: { draftId: string }) => void;
clearAll: () => void;
+5 -8
View File
@@ -97,7 +97,7 @@ export const useDownloadStore = create<DownloadState>()((set, get) => ({
const downloadUrl = buildDownloadUrl(
downloadTarget.baseUrl,
tokenResponse.token,
Platform.OS === "web" ? downloadTarget.authCredentials : null
Platform.OS === "web" ? downloadTarget.authCredentials : null,
);
if (Platform.OS === "web") {
@@ -135,7 +135,7 @@ export const useDownloadStore = create<DownloadState>()((set, get) => ({
speed,
eta,
});
}
},
);
const result = await downloadResumable.downloadAsync();
@@ -203,9 +203,7 @@ export const useDownloadStore = create<DownloadState>()((set, get) => ({
const updated = new Map(state.downloads);
updated.delete(id);
const newActiveId =
state.activeDownloadId === id
? findMostRecentDownloadId(updated)
: state.activeDownloadId;
state.activeDownloadId === id ? findMostRecentDownloadId(updated) : state.activeDownloadId;
return { downloads: updated, activeDownloadId: newActiveId };
});
},
@@ -245,8 +243,7 @@ type DownloadTarget = {
};
function resolveDaemonDownloadTarget(daemon?: HostProfile): DownloadTarget {
const endpoint =
daemon?.connections.find((conn) => conn.type === "directTcp")?.endpoint ?? null;
const endpoint = daemon?.connections.find((conn) => conn.type === "directTcp")?.endpoint ?? null;
if (!endpoint) {
return { baseUrl: null, authHeader: null, authCredentials: null };
}
@@ -287,7 +284,7 @@ function resolveDaemonDownloadTarget(daemon?: HostProfile): DownloadTarget {
function buildDownloadUrl(
baseUrl: string,
token: string,
authCredentials: { username: string; password: string } | null
authCredentials: { username: string; password: string } | null,
): string {
const url = new URL("/api/files/download", baseUrl);
url.searchParams.set("token", token);
+18 -27
View File
@@ -46,10 +46,7 @@ interface DraftStoreActions {
getDraftInput: (draftKey: string) => DraftInput | undefined;
hydrateDraftInput: (draftKey: string) => Promise<DraftInput | undefined>;
saveDraftInput: (input: { draftKey: string; draft: DraftInput }) => void;
markDraftLifecycle: (input: {
draftKey: string;
lifecycle: DraftLifecycleState;
}) => void;
markDraftLifecycle: (input: { draftKey: string; lifecycle: DraftLifecycleState }) => void;
clearDraftInput: (input: {
draftKey: string;
lifecycle?: Exclude<DraftLifecycleState, "active">;
@@ -57,10 +54,7 @@ interface DraftStoreActions {
getCreateModalDraft: () => DraftInput | null;
saveCreateModalDraft: (draft: DraftInput | null) => void;
beginDraftGeneration: (draftKey: string) => number;
isDraftGenerationCurrent: (input: {
draftKey: string;
generation: number;
}) => boolean;
isDraftGenerationCurrent: (input: { draftKey: string; generation: number }) => boolean;
collectActiveAttachmentIds: () => string[];
}
@@ -158,9 +152,7 @@ function toDraftInputIfReady(record: DraftRecord | null | undefined): DraftInput
}
return {
text: record.input.text,
images: (record.input.images as AttachmentMetadata[]).map(
normalizeAttachmentMetadata
),
images: (record.input.images as AttachmentMetadata[]).map(normalizeAttachmentMetadata),
};
}
@@ -197,10 +189,7 @@ function pruneFinalizedDraftRecords(input: {
let changed = false;
const next: Record<string, DraftRecord> = {};
for (const [draftKey, record] of Object.entries(input.drafts)) {
if (
record.lifecycle !== "active" &&
input.nowMs - record.updatedAt >= FINALIZED_DRAFT_TTL_MS
) {
if (record.lifecycle !== "active" && input.nowMs - record.updatedAt >= FINALIZED_DRAFT_TTL_MS) {
changed = true;
continue;
}
@@ -331,7 +320,9 @@ async function migrateAllLegacyDrafts(): Promise<void> {
}
}
async function migrateLegacyImages(images: readonly PersistedDraftImage[]): Promise<AttachmentMetadata[]> {
async function migrateLegacyImages(
images: readonly PersistedDraftImage[],
): Promise<AttachmentMetadata[]> {
if (images.length === 0) {
return [];
}
@@ -364,7 +355,7 @@ async function migrateLegacyImages(images: readonly PersistedDraftImage[]): Prom
});
return null;
}
})
}),
);
return migrated.filter((entry): entry is AttachmentMetadata => entry !== null);
@@ -389,9 +380,9 @@ function migratePersistedState(state: unknown): DraftStoreState {
input: {
text: typeof recordInput.text === "string" ? recordInput.text : "",
images: Array.isArray(recordInput.images)
? (recordInput.images
? recordInput.images
.map((entry) => normalizePersistedImage(entry))
.filter((entry): entry is PersistedDraftImage => entry !== null))
.filter((entry): entry is PersistedDraftImage => entry !== null)
: [],
},
lifecycle:
@@ -410,9 +401,9 @@ function migratePersistedState(state: unknown): DraftStoreState {
input: {
text: typeof legacy.text === "string" ? legacy.text : "",
images: Array.isArray(legacy.images)
? (legacy.images
? legacy.images
.map((entry) => normalizePersistedImage(entry))
.filter((entry): entry is PersistedDraftImage => entry !== null))
.filter((entry): entry is PersistedDraftImage => entry !== null)
: [],
},
lifecycle: "active",
@@ -430,9 +421,9 @@ function migratePersistedState(state: unknown): DraftStoreState {
input: {
text: typeof recordInput.text === "string" ? recordInput.text : "",
images: Array.isArray(recordInput.images)
? (recordInput.images
? recordInput.images
.map((entry) => normalizePersistedImage(entry))
.filter((entry): entry is PersistedDraftImage => entry !== null))
.filter((entry): entry is PersistedDraftImage => entry !== null)
: [],
},
lifecycle:
@@ -448,9 +439,9 @@ function migratePersistedState(state: unknown): DraftStoreState {
input: {
text: typeof legacy.text === "string" ? legacy.text : "",
images: Array.isArray(legacy.images)
? (legacy.images
? legacy.images
.map((entry) => normalizePersistedImage(entry))
.filter((entry): entry is PersistedDraftImage => entry !== null))
.filter((entry): entry is PersistedDraftImage => entry !== null)
: [],
},
lifecycle: "active",
@@ -631,8 +622,8 @@ export const useDraftStore = create<DraftStore>()(
scheduleAttachmentGc();
};
},
}
)
},
),
);
export const __draftStoreTestUtils = {
@@ -22,24 +22,22 @@ interface KeyboardShortcutsState {
resetModifiers: () => void;
}
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>(
(set) => ({
commandCenterOpen: false,
projectPickerOpen: false,
shortcutsDialogOpen: false,
altDown: false,
cmdOrCtrlDown: false,
sidebarShortcutWorkspaceTargets: [],
visibleWorkspaceTargets: [],
export const useKeyboardShortcutsStore = create<KeyboardShortcutsState>((set) => ({
commandCenterOpen: false,
projectPickerOpen: false,
shortcutsDialogOpen: false,
altDown: false,
cmdOrCtrlDown: false,
sidebarShortcutWorkspaceTargets: [],
visibleWorkspaceTargets: [],
setCommandCenterOpen: (open) => set({ commandCenterOpen: open }),
setProjectPickerOpen: (open) => set({ projectPickerOpen: open }),
setShortcutsDialogOpen: (open) => set({ shortcutsDialogOpen: open }),
setAltDown: (down) => set({ altDown: down }),
setCmdOrCtrlDown: (down) => set({ cmdOrCtrlDown: down }),
setSidebarShortcutWorkspaceTargets: (targets) =>
set({ sidebarShortcutWorkspaceTargets: targets }),
setVisibleWorkspaceTargets: (targets) => set({ visibleWorkspaceTargets: targets }),
resetModifiers: () => set({ altDown: false, cmdOrCtrlDown: false }),
})
);
setCommandCenterOpen: (open) => set({ commandCenterOpen: open }),
setProjectPickerOpen: (open) => set({ projectPickerOpen: open }),
setShortcutsDialogOpen: (open) => set({ shortcutsDialogOpen: open }),
setAltDown: (down) => set({ altDown: down }),
setCmdOrCtrlDown: (down) => set({ cmdOrCtrlDown: down }),
setSidebarShortcutWorkspaceTargets: (targets) =>
set({ sidebarShortcutWorkspaceTargets: targets }),
setVisibleWorkspaceTargets: (targets) => set({ visibleWorkspaceTargets: targets }),
resetModifiers: () => set({ altDown: false, cmdOrCtrlDown: false }),
}));
@@ -36,10 +36,7 @@ function getSnapshot(): ActiveWorkspaceSelection | null {
}
function emitIfChanged(next: ActiveWorkspaceSelection | null) {
if (
snapshot?.serverId === next?.serverId &&
snapshot?.workspaceId === next?.workspaceId
) {
if (snapshot?.serverId === next?.serverId && snapshot?.workspaceId === next?.workspaceId) {
return;
}
snapshot = next;
@@ -48,7 +45,9 @@ function emitIfChanged(next: ActiveWorkspaceSelection | null) {
}
}
function extractActiveWorkspaceFromRoute(route: NavigationRouteLike | undefined): ActiveWorkspaceSelection | null {
function extractActiveWorkspaceFromRoute(
route: NavigationRouteLike | undefined,
): ActiveWorkspaceSelection | null {
if (!route) {
return null;
}
@@ -84,11 +83,11 @@ function extractActiveWorkspaceFromRoute(route: NavigationRouteLike | undefined)
return { serverId, workspaceId };
}
export function syncNavigationActiveWorkspace(
navigationRef: NavigationObserverRef
) {
export function syncNavigationActiveWorkspace(navigationRef: NavigationObserverRef) {
emitIfChanged(
extractActiveWorkspaceFromRoute(navigationRef.current?.getCurrentRoute() as NavigationRouteLike | undefined)
extractActiveWorkspaceFromRoute(
navigationRef.current?.getCurrentRoute() as NavigationRouteLike | undefined,
),
);
}
+5 -5
View File
@@ -15,7 +15,7 @@ describe("panel-store explorer tab resolution", () => {
cwd,
isGit: true,
explorerTabByCheckout: {},
})
}),
).toBe("changes");
});
@@ -26,7 +26,7 @@ describe("panel-store explorer tab resolution", () => {
cwd,
isGit: false,
explorerTabByCheckout: {},
})
}),
).toBe("files");
});
@@ -40,7 +40,7 @@ describe("panel-store explorer tab resolution", () => {
explorerTabByCheckout: {
[key]: "files",
},
})
}),
).toBe("files");
});
@@ -54,7 +54,7 @@ describe("panel-store explorer tab resolution", () => {
explorerTabByCheckout: {
[key]: "terminals" as any,
},
})
}),
).toBe("changes");
});
@@ -68,7 +68,7 @@ describe("panel-store explorer tab resolution", () => {
explorerTabByCheckout: {
[key]: "changes",
},
})
}),
).toBe("files");
});
});
+14 -12
View File
@@ -150,8 +150,7 @@ export const usePanelStore = create<PanelState>()(
}),
closeFileExplorer: () =>
set((state) => ({
mobileView:
state.mobileView === "file-explorer" ? ("agent" as const) : state.mobileView,
mobileView: state.mobileView === "file-explorer" ? ("agent" as const) : state.mobileView,
desktop: {
...state.desktop,
fileExplorerOpen: false,
@@ -164,8 +163,7 @@ export const usePanelStore = create<PanelState>()(
// On desktop, closing depends on which panel triggered it
// This is called when closing via gesture/backdrop, so we close the currently active mobile panel
desktop: {
agentListOpen:
state.mobileView === "agent-list" ? false : state.desktop.agentListOpen,
agentListOpen: state.mobileView === "agent-list" ? false : state.desktop.agentListOpen,
fileExplorerOpen:
state.mobileView === "file-explorer" ? false : state.desktop.fileExplorerOpen,
},
@@ -174,7 +172,9 @@ export const usePanelStore = create<PanelState>()(
toggleAgentList: () =>
set((state) => ({
// Mobile: toggle between agent and agent-list
mobileView: (state.mobileView === "agent-list" ? "agent" : "agent-list") as MobilePanelView,
mobileView: (state.mobileView === "agent-list"
? "agent"
: "agent-list") as MobilePanelView,
desktop: {
...state.desktop,
agentListOpen: !state.desktop.agentListOpen,
@@ -186,9 +186,7 @@ export const usePanelStore = create<PanelState>()(
// Mobile: toggle between agent and file-explorer
const willOpenMobile = state.mobileView !== "file-explorer";
const willOpenDesktop = !state.desktop.fileExplorerOpen;
const nextMobileView: MobilePanelView = willOpenMobile
? "file-explorer"
: "agent";
const nextMobileView: MobilePanelView = willOpenMobile ? "file-explorer" : "agent";
const nextDesktop = {
...state.desktop,
fileExplorerOpen: willOpenDesktop,
@@ -275,7 +273,7 @@ export const usePanelStore = create<PanelState>()(
state.explorerFilesSplitRatio = DEFAULT_EXPLORER_FILES_SPLIT_RATIO;
} else {
state.explorerFilesSplitRatio = clampExplorerFilesSplitRatio(
state.explorerFilesSplitRatio
state.explorerFilesSplitRatio,
);
}
}
@@ -294,7 +292,11 @@ export const usePanelStore = create<PanelState>()(
state.explorerTab = "changes";
}
if (version < 4 || typeof state.explorerTabByCheckout !== "object" || !state.explorerTabByCheckout) {
if (
version < 4 ||
typeof state.explorerTabByCheckout !== "object" ||
!state.explorerTabByCheckout
) {
state.explorerTabByCheckout = {};
} else {
const entries = Object.entries(state.explorerTabByCheckout as Record<string, unknown>);
@@ -321,8 +323,8 @@ export const usePanelStore = create<PanelState>()(
explorerSortOption: state.explorerSortOption,
explorerFilesSplitRatio: state.explorerFilesSplitRatio,
}),
}
)
},
),
);
/**
@@ -36,8 +36,8 @@ export const useSectionOrderStore = create<SectionOrderState>()(
partialize: (state) => ({
projectOrder: state.projectOrder,
}),
}
)
},
),
);
/**
@@ -46,7 +46,7 @@ export const useSectionOrderStore = create<SectionOrderState>()(
*/
export function sortProjectsByStoredOrder<T extends { projectKey: string }>(
groups: T[],
storedOrder: string[]
storedOrder: string[],
): T[] {
if (storedOrder.length === 0) {
return groups;
File diff suppressed because it is too large Load Diff
@@ -6,29 +6,26 @@ interface SidebarCollapsedSectionsState {
setProjectCollapsed: (projectKey: string, collapsed: boolean) => void;
}
export const useSidebarCollapsedSectionsStore = create<SidebarCollapsedSectionsState>(
(set) => ({
collapsedProjectKeys: new Set(),
toggleProjectCollapsed: (projectKey) =>
set((state) => {
const next = new Set(state.collapsedProjectKeys);
if (next.has(projectKey)) {
next.delete(projectKey);
} else {
next.add(projectKey);
}
return { collapsedProjectKeys: next };
}),
setProjectCollapsed: (projectKey, collapsed) =>
set((state) => {
const next = new Set(state.collapsedProjectKeys);
if (collapsed) {
next.add(projectKey);
} else {
next.delete(projectKey);
}
return { collapsedProjectKeys: next };
}),
})
);
export const useSidebarCollapsedSectionsStore = create<SidebarCollapsedSectionsState>((set) => ({
collapsedProjectKeys: new Set(),
toggleProjectCollapsed: (projectKey) =>
set((state) => {
const next = new Set(state.collapsedProjectKeys);
if (next.has(projectKey)) {
next.delete(projectKey);
} else {
next.add(projectKey);
}
return { collapsedProjectKeys: next };
}),
setProjectCollapsed: (projectKey, collapsed) =>
set((state) => {
const next = new Set(state.collapsedProjectKeys);
if (collapsed) {
next.add(projectKey);
} else {
next.delete(projectKey);
}
return { collapsedProjectKeys: next };
}),
}));
+39 -39
View File
@@ -1,34 +1,34 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { create } from 'zustand'
import { createJSONStorage, persist } from 'zustand/middleware'
import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
interface SidebarOrderStoreState {
projectOrderByServerId: Record<string, string[]>
workspaceOrderByServerAndProject: Record<string, string[]>
getProjectOrder: (serverId: string) => string[]
setProjectOrder: (serverId: string, keys: string[]) => void
getWorkspaceOrder: (serverId: string, projectKey: string) => string[]
setWorkspaceOrder: (serverId: string, projectKey: string, keys: string[]) => void
projectOrderByServerId: Record<string, string[]>;
workspaceOrderByServerAndProject: Record<string, string[]>;
getProjectOrder: (serverId: string) => string[];
setProjectOrder: (serverId: string, keys: string[]) => void;
getWorkspaceOrder: (serverId: string, projectKey: string) => string[];
setWorkspaceOrder: (serverId: string, projectKey: string, keys: string[]) => void;
}
function normalizeKeys(keys: string[]): string[] {
const seen = new Set<string>()
const normalized: string[] = []
const seen = new Set<string>();
const normalized: string[] = [];
for (const rawKey of keys) {
const key = rawKey.trim()
const key = rawKey.trim();
if (!key || seen.has(key)) {
continue
continue;
}
seen.add(key)
normalized.push(key)
seen.add(key);
normalized.push(key);
}
return normalized
return normalized;
}
function buildWorkspaceScopeKey(serverId: string, projectKey: string): string {
return `${serverId.trim()}::${projectKey.trim()}`
return `${serverId.trim()}::${projectKey.trim()}`;
}
export const useSidebarOrderStore = create<SidebarOrderStoreState>()(
@@ -37,57 +37,57 @@ export const useSidebarOrderStore = create<SidebarOrderStoreState>()(
projectOrderByServerId: {},
workspaceOrderByServerAndProject: {},
getProjectOrder: (serverId) => {
const key = serverId.trim()
const key = serverId.trim();
if (!key) {
return []
return [];
}
return get().projectOrderByServerId[key] ?? []
return get().projectOrderByServerId[key] ?? [];
},
setProjectOrder: (serverId, keys) => {
const key = serverId.trim()
const key = serverId.trim();
if (!key) {
return
return;
}
const normalized = normalizeKeys(keys)
const normalized = normalizeKeys(keys);
set((state) => ({
projectOrderByServerId: {
...state.projectOrderByServerId,
[key]: normalized,
},
}))
}));
},
getWorkspaceOrder: (serverId, projectKey) => {
const serverKey = serverId.trim()
const projectScope = projectKey.trim()
const serverKey = serverId.trim();
const projectScope = projectKey.trim();
if (!serverKey || !projectScope) {
return []
return [];
}
const scopeKey = buildWorkspaceScopeKey(serverKey, projectScope)
return get().workspaceOrderByServerAndProject[scopeKey] ?? []
const scopeKey = buildWorkspaceScopeKey(serverKey, projectScope);
return get().workspaceOrderByServerAndProject[scopeKey] ?? [];
},
setWorkspaceOrder: (serverId, projectKey, keys) => {
const serverKey = serverId.trim()
const projectScope = projectKey.trim()
const serverKey = serverId.trim();
const projectScope = projectKey.trim();
if (!serverKey || !projectScope) {
return
return;
}
const scopeKey = buildWorkspaceScopeKey(serverKey, projectScope)
const normalized = normalizeKeys(keys)
const scopeKey = buildWorkspaceScopeKey(serverKey, projectScope);
const normalized = normalizeKeys(keys);
set((state) => ({
workspaceOrderByServerAndProject: {
...state.workspaceOrderByServerAndProject,
[scopeKey]: normalized,
},
}))
}));
},
}),
{
name: 'sidebar-project-workspace-order',
name: "sidebar-project-workspace-order",
storage: createJSONStorage(() => AsyncStorage),
partialize: (state) => ({
projectOrderByServerId: state.projectOrderByServerId,
workspaceOrderByServerAndProject: state.workspaceOrderByServerAndProject,
}),
}
)
)
},
),
);
@@ -217,12 +217,16 @@ function generateNodeId(prefix: "pane" | "group"): string {
return `${prefix}_${randomValue}`;
}
function createPaneNode(input: { id: string; tabs?: WorkspaceTab[]; focusedTabId?: string | null }): SplitNodeInternal {
function createPaneNode(input: {
id: string;
tabs?: WorkspaceTab[];
focusedTabId?: string | null;
}): SplitNodeInternal {
const normalizedTabs = normalizeWorkspaceTabs(input.tabs ?? []);
const tabIds = normalizedTabs.map((tab) => tab.tabId);
const focusedTabId = tabIds.includes(input.focusedTabId ?? "")
? (input.focusedTabId ?? null)
: tabIds[tabIds.length - 1] ?? null;
: (tabIds[tabIds.length - 1] ?? null);
return {
kind: "pane",
@@ -370,11 +374,18 @@ function asInternalNode(node: SplitNode): SplitNodeInternal {
return node as SplitNodeInternal;
}
function asInternalLayout(layout: WorkspaceLayout): { root: SplitNodeInternal; focusedPaneId: string } {
function asInternalLayout(layout: WorkspaceLayout): {
root: SplitNodeInternal;
focusedPaneId: string;
} {
return layout as { root: SplitNodeInternal; focusedPaneId: string };
}
function findPanePathById(node: SplitNodeInternal, paneId: string, path: number[] = []): number[] | null {
function findPanePathById(
node: SplitNodeInternal,
paneId: string,
path: number[] = [],
): number[] | null {
if (node.kind === "pane") {
return node.pane.id === paneId ? path : null;
}
@@ -387,12 +398,19 @@ function findPanePathById(node: SplitNodeInternal, paneId: string, path: number[
return null;
}
function findPanePathContainingTab(node: SplitNodeInternal, tabId: string, path: number[] = []): number[] | null {
function findPanePathContainingTab(
node: SplitNodeInternal,
tabId: string,
path: number[] = [],
): number[] | null {
if (node.kind === "pane") {
return node.pane.tabs.some((tab) => tab.tabId === tabId) ? path : null;
}
for (let index = 0; index < node.group.children.length; index += 1) {
const childPath = findPanePathContainingTab(node.group.children[index], tabId, [...path, index]);
const childPath = findPanePathContainingTab(node.group.children[index], tabId, [
...path,
index,
]);
if (childPath) {
return childPath;
}
@@ -400,7 +418,11 @@ function findPanePathContainingTab(node: SplitNodeInternal, tabId: string, path:
return null;
}
function findGroupPathById(node: SplitNodeInternal, groupId: string, path: number[] = []): number[] | null {
function findGroupPathById(
node: SplitNodeInternal,
groupId: string,
path: number[] = [],
): number[] | null {
if (node.kind === "pane") {
return null;
}
@@ -428,7 +450,7 @@ function getNodeAtPath(node: SplitNodeInternal, path: number[]): SplitNodeIntern
function replaceNodeAtPath(
node: SplitNodeInternal,
path: number[],
updater: (node: SplitNodeInternal) => SplitNodeInternal
updater: (node: SplitNodeInternal) => SplitNodeInternal,
): SplitNodeInternal {
if (path.length === 0) {
return updater(node);
@@ -437,7 +459,7 @@ function replaceNodeAtPath(
invariant(node.kind === "group", "Expected group while replacing split tree node");
const [index, ...rest] = path;
const nextChildren = node.group.children.map((child, childIndex) =>
childIndex === index ? replaceNodeAtPath(child, rest, updater) : child
childIndex === index ? replaceNodeAtPath(child, rest, updater) : child,
);
return createGroupNode({
@@ -450,7 +472,7 @@ function replaceNodeAtPath(
function insertChildIntoGroup(
groupNode: SplitNodeInternal,
input: InsertChildIntoGroupInput
input: InsertChildIntoGroupInput,
): SplitNodeInternal {
invariant(groupNode.kind === "group", "Expected group for split insertion");
const nextChildren = groupNode.group.children.slice();
@@ -509,7 +531,7 @@ function normalizePaneAfterTabChange(pane: SplitPaneInternal): SplitPaneInternal
const tabIds = tabs.map((tab) => tab.tabId);
const focusedTabId = tabIds.includes(pane.focusedTabId ?? "")
? pane.focusedTabId
: tabIds[tabIds.length - 1] ?? null;
: (tabIds[tabIds.length - 1] ?? null);
return {
id: pane.id,
@@ -561,7 +583,9 @@ function normalizeNode(node: unknown): SplitNodeInternal | null {
}
const children = Array.isArray(rawGroup.children)
? rawGroup.children.map((child) => normalizeNode(child)).filter((child): child is SplitNodeInternal => child !== null)
? rawGroup.children
.map((child) => normalizeNode(child))
.filter((child): child is SplitNodeInternal => child !== null)
: [];
if (children.length === 0) {
return null;
@@ -639,7 +663,7 @@ function removePaneByPath(root: SplitNodeInternal, path: number[]): SplitNodeInt
function detachTabFromTree(
root: SplitNodeInternal,
input: DetachTabFromTreeInput
input: DetachTabFromTreeInput,
): DetachTabFromTreeResult {
const panePath = findPanePathContainingTab(root, input.tabId);
if (!panePath) {
@@ -670,7 +694,10 @@ function detachTabFromTree(
};
}
function insertTabIntoPane(root: SplitNodeInternal, input: InsertTabIntoPaneInput): SplitNodeInternal {
function insertTabIntoPane(
root: SplitNodeInternal,
input: InsertTabIntoPaneInput,
): SplitNodeInternal {
const panePath = findPanePathById(root, input.paneId);
invariant(panePath, `Pane not found: ${input.paneId}`);
return replaceNodeAtPath(root, panePath, (node) => {
@@ -716,14 +743,17 @@ function updateTabInTree(root: SplitNodeInternal, input: UpdateTabInTreeInput):
pane: normalizePaneAfterTabChange({
...node.pane,
tabs: node.pane.tabs.map((tab) =>
tab.tabId === input.tabId ? { ...tab, target: input.target } : tab
tab.tabId === input.tabId ? { ...tab, target: input.target } : tab,
),
}),
};
});
}
function updateGroupSizesInTree(root: SplitNodeInternal, input: UpdateGroupSizesInTreeInput): SplitNodeInternal {
function updateGroupSizesInTree(
root: SplitNodeInternal,
input: UpdateGroupSizesInTreeInput,
): SplitNodeInternal {
const groupPath = findGroupPathById(root, input.groupId);
if (!groupPath) {
return root;
@@ -742,7 +772,10 @@ function updateGroupSizesInTree(root: SplitNodeInternal, input: UpdateGroupSizes
});
}
function updatePaneInTree(root: SplitNodeInternal, input: UpdatePaneInTreeInput): SplitNodeInternal {
function updatePaneInTree(
root: SplitNodeInternal,
input: UpdatePaneInTreeInput,
): SplitNodeInternal {
const panePath = findPanePathById(root, input.paneId);
if (!panePath) {
return root;
@@ -757,7 +790,8 @@ function updatePaneInTree(root: SplitNodeInternal, input: UpdatePaneInTreeInput)
}
function insertSplitInternal(input: InsertSplitInternalInput): InsertSplitInternalResult {
const direction = input.position === "left" || input.position === "right" ? "horizontal" : "vertical";
const direction =
input.position === "left" || input.position === "right" ? "horizontal" : "vertical";
const insertAfter = input.position === "right" || input.position === "bottom";
const targetPathBeforeDetach = findPanePathById(input.root, input.targetPaneId);
@@ -783,10 +817,7 @@ function insertSplitInternal(input: InsertSplitInternalInput): InsertSplitIntern
const parentPath = targetPath.slice(0, -1);
const targetIndex = targetPath[targetPath.length - 1] ?? 0;
const parentNode =
parentPath.length > 0
? getNodeAtPath(detached.root, parentPath)
: null;
const parentNode = parentPath.length > 0 ? getNodeAtPath(detached.root, parentPath) : null;
if (parentNode?.kind === "group" && parentNode.group.direction === direction) {
const targetSize = parentNode.group.sizes[targetIndex] ?? 0;
@@ -801,7 +832,7 @@ function insertSplitInternal(input: InsertSplitInternalInput): InsertSplitIntern
index: insertIndex,
node: newPaneNode,
sizes: nextSizes,
})
}),
),
newPaneId,
};
@@ -902,7 +933,7 @@ export function insertSplit(
root: SplitNode,
targetPaneId: string,
tabId: string,
position: "left" | "right" | "top" | "bottom"
position: "left" | "right" | "top" | "bottom",
): SplitNode {
return insertSplitInternal({
root: asInternalNode(root),
@@ -929,15 +960,16 @@ export function removeTabFromTree(root: SplitNode, tabId: string): SplitNode {
export function openTabInLayout(input: OpenTabInLayoutInput): OpenTabInLayoutResult {
const layout = asInternalLayout(input.layout);
const existingTab = collectAllTabs(layout.root).find((tab) =>
workspaceTabTargetsEqual(tab.target, input.target)
workspaceTabTargetsEqual(tab.target, input.target),
);
if (existingTab) {
return {
tabId: existingTab.tabId,
layout: focusTabInLayout({
layout,
tabId: existingTab.tabId,
}) ?? input.layout,
layout:
focusTabInLayout({
layout,
tabId: existingTab.tabId,
}) ?? input.layout,
};
}
@@ -1005,7 +1037,9 @@ export function focusTabInLayout(input: FocusTabInLayoutInput): WorkspaceLayout
};
}
export function retargetTabInLayout(input: RetargetTabInLayoutInput): RetargetTabInLayoutResult | null {
export function retargetTabInLayout(
input: RetargetTabInLayoutInput,
): RetargetTabInLayoutResult | null {
const layout = asInternalLayout(input.layout);
const pane = findPaneContainingTab(layout.root, input.tabId);
if (!pane) {
@@ -1033,7 +1067,7 @@ export function retargetTabInLayout(input: RetargetTabInLayoutInput): RetargetTa
}
export function reorderFocusedPaneTabsInLayout(
input: ReorderFocusedPaneTabsInLayoutInput
input: ReorderFocusedPaneTabsInLayoutInput,
): WorkspaceLayout | null {
const layout = asInternalLayout(input.layout);
if (!findPaneById(layout.root, layout.focusedPaneId)) {
@@ -1078,13 +1112,16 @@ export function splitPaneInLayout(input: SplitPaneInLayoutInput): SplitPaneInLay
};
}
export function splitPaneEmptyInLayout(input: SplitPaneEmptyInLayoutInput): SplitPaneInLayoutResult | null {
export function splitPaneEmptyInLayout(
input: SplitPaneEmptyInLayoutInput,
): SplitPaneInLayoutResult | null {
const layout = asInternalLayout(input.layout);
if (!findPaneById(layout.root, input.targetPaneId)) {
return null;
}
const direction = input.position === "left" || input.position === "right" ? "horizontal" : "vertical";
const direction =
input.position === "left" || input.position === "right" ? "horizontal" : "vertical";
const insertAfter = input.position === "right" || input.position === "bottom";
const targetPath = findPanePathById(layout.root, input.targetPaneId);
@@ -1107,7 +1144,7 @@ export function splitPaneEmptyInLayout(input: SplitPaneEmptyInLayoutInput): Spli
nextSizes.splice(insertIndex, 0, targetSize / 2);
nextSizes[targetIndex + (insertAfter ? 0 : 1)] = targetSize / 2;
nextRoot = replaceNodeAtPath(layout.root, parentPath, () =>
insertChildIntoGroup(parentNode, { index: insertIndex, node: newPaneNode, sizes: nextSizes })
insertChildIntoGroup(parentNode, { index: insertIndex, node: newPaneNode, sizes: nextSizes }),
);
} else {
const newGroup = createGroupNode({
@@ -1181,7 +1218,9 @@ export function resizeSplitInLayout(input: ResizeSplitInLayoutInput): WorkspaceL
};
}
export function reorderPaneTabsInLayout(input: ReorderPaneTabsInLayoutInput): WorkspaceLayout | null {
export function reorderPaneTabsInLayout(
input: ReorderPaneTabsInLayoutInput,
): WorkspaceLayout | null {
const layout = asInternalLayout(input.layout);
if (!findPaneById(layout.root, input.paneId)) {
return null;
@@ -190,7 +190,9 @@ describe("workspace-layout-store tree transforms", () => {
expect(nextGroup.group.sizes).toEqual([0.2, 0.8]);
expect(collectAllPanes(nextRoot).map((pane) => pane.id)).toEqual(["left", "bottom-right"]);
expect(nextGroup.group.children[1]).toEqual(createPane({ id: "bottom-right", tabIds: ["tab-c"] }));
expect(nextGroup.group.children[1]).toEqual(
createPane({ id: "bottom-right", tabIds: ["tab-c"] }),
);
});
it("removeTabFromTree collapses empty panes but keeps the final root pane", () => {
@@ -228,7 +230,7 @@ describe("workspace-layout-store actions", () => {
it("opens tabs into the focused pane and focuses duplicate opens instead of creating them", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -244,7 +246,10 @@ describe("workspace-layout-store actions", () => {
expect(splitPaneId).toBe("pane_aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
store.focusPane(workspaceKey, "main");
const duplicateTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" });
const duplicateTabId = store.openTab(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
expect(firstTabId).toBe("file_/repo/worktree/a.ts");
@@ -259,7 +264,7 @@ describe("workspace-layout-store actions", () => {
it("focusTab moves workspace focus to the pane containing the tab", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -285,7 +290,7 @@ describe("workspace-layout-store actions", () => {
it("retargetTab updates the existing tab target without moving it to a different pane", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"12121212-1212-1212-1212-121212121212"
"12121212-1212-1212-1212-121212121212",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -333,16 +338,28 @@ describe("workspace-layout-store actions", () => {
tabIds: [thirdTabId!, firstTabId!, secondTabId!],
focusedTabId: thirdTabId,
tabs: [
{ tabId: thirdTabId, target: { kind: "file", path: "/repo/worktree/c.ts" }, createdAt: expect.any(Number) },
{ tabId: firstTabId, target: { kind: "file", path: "/repo/worktree/a.ts" }, createdAt: expect.any(Number) },
{ tabId: secondTabId, target: { kind: "file", path: "/repo/worktree/b.ts" }, createdAt: expect.any(Number) },
{
tabId: thirdTabId,
target: { kind: "file", path: "/repo/worktree/c.ts" },
createdAt: expect.any(Number),
},
{
tabId: firstTabId,
target: { kind: "file", path: "/repo/worktree/a.ts" },
createdAt: expect.any(Number),
},
{
tabId: secondTabId,
target: { kind: "file", path: "/repo/worktree/b.ts" },
createdAt: expect.any(Number),
},
],
});
});
it("reorderTabsInPane reorders tabs in the requested pane without changing focused pane", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"34343434-3434-3434-3434-343434343434"
"34343434-3434-3434-3434-343434343434",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -369,15 +386,23 @@ describe("workspace-layout-store actions", () => {
tabIds: [fourthTabId!, thirdTabId!],
focusedTabId: fourthTabId,
tabs: [
{ tabId: fourthTabId, target: { kind: "file", path: "/repo/worktree/d.ts" }, createdAt: expect.any(Number) },
{ tabId: thirdTabId, target: { kind: "file", path: "/repo/worktree/c.ts" }, createdAt: expect.any(Number) },
{
tabId: fourthTabId,
target: { kind: "file", path: "/repo/worktree/d.ts" },
createdAt: expect.any(Number),
},
{
tabId: thirdTabId,
target: { kind: "file", path: "/repo/worktree/c.ts" },
createdAt: expect.any(Number),
},
],
});
});
it("focusPane switches workspace focus to a different pane", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"56565656-5656-5656-5656-565656565656"
"56565656-5656-5656-5656-565656565656",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -403,7 +428,7 @@ describe("workspace-layout-store actions", () => {
it("closeTab collapses an emptied pane and keeps the nearest sibling focused", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"cccccccc-cccc-cccc-cccc-cccccccccccc"
"cccccccc-cccc-cccc-cccc-cccccccccccc",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -475,7 +500,7 @@ describe("workspace-layout-store actions", () => {
it("moveTabToPane collapses the source pane when its last tab moves out", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"dddddddd-dddd-dddd-dddd-dddddddddddd"
"dddddddd-dddd-dddd-dddd-dddddddddddd",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -542,7 +567,7 @@ describe("workspace-layout-store actions", () => {
tabIds: child.pane.tabIds,
focusedTabId: child.pane.focusedTabId,
};
})
}),
).toEqual([
{
id: "main",
@@ -560,7 +585,7 @@ describe("workspace-layout-store actions", () => {
it("openTab focuses the existing tab instead of creating a duplicate entry", () => {
vi.spyOn(globalThis.crypto, "randomUUID").mockReturnValue(
"abababab-abab-abab-abab-abababababab"
"abababab-abab-abab-abab-abababababab",
);
const workspaceKey = createWorkspaceKey();
const store = useWorkspaceLayoutStore.getState();
@@ -574,7 +599,10 @@ describe("workspace-layout-store actions", () => {
});
store.focusPane(workspaceKey, "main");
const duplicateTabId = store.openTab(workspaceKey, { kind: "file", path: "/repo/worktree/b.ts" });
const duplicateTabId = store.openTab(workspaceKey, {
kind: "file",
path: "/repo/worktree/b.ts",
});
const layout = useWorkspaceLayoutStore.getState().layoutByWorkspace[workspaceKey]!;
expect(splitPaneId).toBe("pane_abababab-abab-abab-abab-abababababab");
@@ -66,14 +66,14 @@ interface WorkspaceLayoutStore {
tabId: string;
targetPaneId: string;
position: "left" | "right" | "top" | "bottom";
}
},
) => string | null;
splitPaneEmpty: (
workspaceKey: string,
input: {
targetPaneId: string;
position: "left" | "right" | "top" | "bottom";
}
},
) => string | null;
moveTabToPane: (workspaceKey: string, tabId: string, toPaneId: string) => void;
focusPane: (workspaceKey: string, paneId: string) => void;
@@ -93,7 +93,10 @@ function trimNonEmpty(value: string | null | undefined): string | null {
return trimmed.length > 0 ? trimmed : null;
}
function getWorkspaceLayout(state: Record<string, WorkspaceLayout>, workspaceKey: string): WorkspaceLayout {
function getWorkspaceLayout(
state: Record<string, WorkspaceLayout>,
workspaceKey: string,
): WorkspaceLayout {
return normalizeLayout(state[workspaceKey] ?? createDefaultLayout());
}
@@ -227,7 +230,9 @@ export const useWorkspaceLayoutStore = create<WorkspaceLayoutStore>()(
if (!normalizedWorkspaceKey) {
return [];
}
return collectAllTabs(getWorkspaceLayout(get().layoutByWorkspace, normalizedWorkspaceKey).root);
return collectAllTabs(
getWorkspaceLayout(get().layoutByWorkspace, normalizedWorkspaceKey).root,
);
},
splitPane: (workspaceKey, input) => {
const normalizedWorkspaceKey = trimNonEmpty(workspaceKey);
@@ -464,6 +469,6 @@ export const useWorkspaceLayoutStore = create<WorkspaceLayoutStore>()(
splitSizesByWorkspace: state.splitSizesByWorkspace,
};
},
}
)
},
),
);
@@ -15,7 +15,10 @@ vi.mock("@react-native-async-storage/async-storage", () => {
};
});
import { buildWorkspaceTabPersistenceKey, useWorkspaceTabsStore } from "@/stores/workspace-tabs-store";
import {
buildWorkspaceTabPersistenceKey,
useWorkspaceTabsStore,
} from "@/stores/workspace-tabs-store";
const SERVER_ID = "server-1";
const WORKSPACE_ID = "/repo/worktree";
@@ -97,7 +100,7 @@ describe("workspace-tabs-store retargetTab", () => {
});
expect(focusedTabId).toBe("terminal_term-1");
expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe(
"terminal_term-1"
"terminal_term-1",
);
});
@@ -129,7 +132,7 @@ describe("workspace-tabs-store retargetTab", () => {
const tabs = state.uiTabsByWorkspace[workspaceKey] ?? [];
const order = state.tabOrderByWorkspace[workspaceKey] ?? [];
const matchingTabs = tabs.filter(
(tab) => tab.target.kind === "agent" && tab.target.agentId === "created-agent"
(tab) => tab.target.kind === "agent" && tab.target.agentId === "created-agent",
);
expect(ensured).toBe(draftTabId);
@@ -162,7 +165,7 @@ describe("workspace-tabs-store retargetTab", () => {
});
expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe(
focusedFileTabId
focusedFileTabId,
);
});
@@ -185,7 +188,7 @@ describe("workspace-tabs-store retargetTab", () => {
expect(fileTabId).toBe("file_/repo/worktree/src/index.ts");
expect(terminalTabId).toBe("terminal_term-1");
expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe(
terminalTabId
terminalTabId,
);
const reopenedFileTabId = useWorkspaceTabsStore.getState().openOrFocusTab({
@@ -195,8 +198,6 @@ describe("workspace-tabs-store retargetTab", () => {
});
expect(reopenedFileTabId).toBe(fileTabId);
expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe(
fileTabId
);
expect(useWorkspaceTabsStore.getState().focusedTabIdByWorkspace[workspaceKey]).toBe(fileTabId);
});
});
+39 -13
View File
@@ -38,7 +38,9 @@ export function buildWorkspaceTabPersistenceKey(input: {
return `${serverId}:${normalizeWorkspaceId(workspaceId)}`;
}
function normalizeTabTarget(value: WorkspaceTabTarget | null | undefined): WorkspaceTabTarget | null {
function normalizeTabTarget(
value: WorkspaceTabTarget | null | undefined,
): WorkspaceTabTarget | null {
if (!value || typeof value !== "object" || typeof value.kind !== "string") {
return null;
}
@@ -121,7 +123,11 @@ type WorkspaceTabsState = {
uiTabsByWorkspace: Record<string, WorkspaceTab[]>;
tabOrderByWorkspace: Record<string, string[]>;
focusedTabIdByWorkspace: Record<string, string>;
openDraftTab: (input: { serverId: string; workspaceId: string; draftId: string }) => string | null;
openDraftTab: (input: {
serverId: string;
workspaceId: string;
draftId: string;
}) => string | null;
ensureTab: (input: {
serverId: string;
workspaceId: string;
@@ -184,14 +190,17 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
const existingIndex = currentTabs.findIndex((tab) => tab.tabId === effectiveTabId);
const nextTabs = (() => {
if (existingIndex < 0) {
return [...currentTabs, { tabId: effectiveTabId, target: normalizedTarget, createdAt: now }];
return [
...currentTabs,
{ tabId: effectiveTabId, target: normalizedTarget, createdAt: now },
];
}
const existing = currentTabs[existingIndex];
if (existing && tabTargetsEqual(existing.target, normalizedTarget)) {
return currentTabs;
}
return currentTabs.map((tab, index) =>
index === existingIndex ? { ...tab, target: normalizedTarget } : tab
index === existingIndex ? { ...tab, target: normalizedTarget } : tab,
);
})();
@@ -271,7 +280,9 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
const currentFocused = state.focusedTabIdByWorkspace[key] ?? null;
const nextFocused =
currentFocused !== normalizedTabId ? currentFocused : nextOrder[nextOrder.length - 1] ?? null;
currentFocused !== normalizedTabId
? currentFocused
: (nextOrder[nextOrder.length - 1] ?? null);
const nextFocusedByWorkspace = (() => {
if (!nextFocused) {
const { [key]: _removed, ...rest } = state.focusedTabIdByWorkspace;
@@ -319,7 +330,7 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
}
const nextTabs = currentTabs.map((tab, tabIndex) =>
tabIndex === index ? { ...tab, target: normalizedTarget } : tab
tabIndex === index ? { ...tab, target: normalizedTarget } : tab,
);
retargetedTabId = normalizedTabId;
return {
@@ -435,12 +446,23 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
const rawState = (legacy as any)?.state ?? legacy ?? {};
const rawUiTabsByWorkspace =
rawState.uiTabsByWorkspace ?? rawState.openTabsByWorkspace ?? legacy?.uiTabsByWorkspace ?? legacy?.openTabsByWorkspace ?? {};
rawState.uiTabsByWorkspace ??
rawState.openTabsByWorkspace ??
legacy?.uiTabsByWorkspace ??
legacy?.openTabsByWorkspace ??
{};
const rawFocused =
rawState.focusedTabIdByWorkspace ?? legacy?.focusedTabIdByWorkspace ?? rawState.lastFocusedTabByWorkspace ?? {};
rawState.focusedTabIdByWorkspace ??
legacy?.focusedTabIdByWorkspace ??
rawState.lastFocusedTabByWorkspace ??
{};
const rawOrder =
rawState.tabOrderByWorkspace ?? legacy?.tabOrderByWorkspace ?? rawState.tabOrderByWorkspace ?? {};
const legacyOrder = rawState.tabOrderByWorkspace ?? rawState.tabOrderLegacyByWorkspace ?? {};
rawState.tabOrderByWorkspace ??
legacy?.tabOrderByWorkspace ??
rawState.tabOrderByWorkspace ??
{};
const legacyOrder =
rawState.tabOrderByWorkspace ?? rawState.tabOrderLegacyByWorkspace ?? {};
const uiTabsByWorkspace: Record<string, WorkspaceTab[]> = {};
const tabOrderByWorkspace: Record<string, string[]> = {};
@@ -544,7 +566,11 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
focusedTabIdByWorkspace[key] = `agent_${value.agentId.trim()}`;
continue;
}
if (value.kind === "terminal" && typeof value.terminalId === "string" && value.terminalId.trim()) {
if (
value.kind === "terminal" &&
typeof value.terminalId === "string" &&
value.terminalId.trim()
) {
focusedTabIdByWorkspace[key] = `terminal_${value.terminalId.trim()}`;
continue;
}
@@ -559,6 +585,6 @@ export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
focusedTabIdByWorkspace,
};
},
}
)
},
),
);