feat: projects → workspaces → tabs

This commit is contained in:
Mohamed Boudra
2026-03-01 18:22:11 +07:00
parent 2e201cab09
commit 080fb74642
43 changed files with 4054 additions and 2654 deletions
@@ -0,0 +1,136 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
import { create } from "zustand";
import { createJSONStorage, persist } from "zustand/middleware";
export type WorkspaceTabTarget =
| { kind: "agent"; agentId: string }
| { kind: "terminal"; terminalId: string };
function trimNonEmpty(value: string | null | undefined): string | null {
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
function normalizeWorkspaceTab(
value: WorkspaceTabTarget | null | undefined
): WorkspaceTabTarget | null {
if (!value || typeof value !== "object" || typeof value.kind !== "string") {
return null;
}
if (value.kind === "agent") {
const agentId = trimNonEmpty(value.agentId);
if (!agentId) {
return null;
}
return { kind: "agent", agentId };
}
if (value.kind === "terminal") {
const terminalId = trimNonEmpty(value.terminalId);
if (!terminalId) {
return null;
}
return { kind: "terminal", terminalId };
}
return null;
}
export function buildWorkspaceTabPersistenceKey(input: {
serverId: string;
workspaceId: string;
}): string | null {
const serverId = trimNonEmpty(input.serverId);
const workspaceId = trimNonEmpty(input.workspaceId);
if (!serverId || !workspaceId) {
return null;
}
return `${serverId}:${workspaceId}`;
}
type WorkspaceTabsState = {
lastFocusedTabByWorkspace: Record<string, WorkspaceTabTarget>;
setLastFocusedTab: (input: {
serverId: string;
workspaceId: string;
tab: WorkspaceTabTarget;
}) => void;
getLastFocusedTab: (input: {
serverId: string;
workspaceId: string;
}) => WorkspaceTabTarget | null;
};
export const useWorkspaceTabsStore = create<WorkspaceTabsState>()(
persist(
(set, get) => ({
lastFocusedTabByWorkspace: {},
setLastFocusedTab: ({ serverId, workspaceId, tab }) => {
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
const normalizedTab = normalizeWorkspaceTab(tab);
if (!key || !normalizedTab) {
return;
}
set((state) => {
const current = state.lastFocusedTabByWorkspace[key];
if (
current &&
current.kind === normalizedTab.kind &&
((current.kind === "agent" && normalizedTab.kind === "agent"
? current.agentId === normalizedTab.agentId
: current.kind === "terminal" && normalizedTab.kind === "terminal"
? current.terminalId === normalizedTab.terminalId
: false))
) {
return state;
}
return {
lastFocusedTabByWorkspace: {
...state.lastFocusedTabByWorkspace,
[key]: normalizedTab,
},
};
});
},
getLastFocusedTab: ({ serverId, workspaceId }) => {
const key = buildWorkspaceTabPersistenceKey({ serverId, workspaceId });
if (!key) {
return null;
}
const value = get().lastFocusedTabByWorkspace[key];
return normalizeWorkspaceTab(value);
},
}),
{
name: "workspace-tabs-state",
version: 1,
storage: createJSONStorage(() => AsyncStorage),
migrate: (persistedState) => {
const state = persistedState as
| {
lastFocusedTabByWorkspace?: Record<string, WorkspaceTabTarget>;
}
| undefined;
const raw = state?.lastFocusedTabByWorkspace ?? {};
const next: Record<string, WorkspaceTabTarget> = {};
for (const key in raw) {
const value = raw[key];
const normalized = normalizeWorkspaceTab(value);
if (normalized) {
next[key] = normalized;
}
}
return {
...state,
lastFocusedTabByWorkspace: next,
};
},
}
)
);