refactor: move workspace tab navigation logic to prepareWorkspaceTab
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Agent } from "@/stores/session-store";
|
||||
import {
|
||||
canOpenAgentTabFromRoute,
|
||||
deriveWorkspaceAgentVisibility,
|
||||
shouldPruneWorkspaceAgentTab,
|
||||
workspaceAgentVisibilityEqual,
|
||||
@@ -89,18 +88,6 @@ describe("workspace agent visibility", () => {
|
||||
expect(result.knownAgentIds.has("other-workspace-agent")).toBe(false);
|
||||
});
|
||||
|
||||
it("allows explicit route open for archived agent once agents are hydrated", () => {
|
||||
const knownAgentIds = new Set(["archived-agent"]);
|
||||
|
||||
expect(
|
||||
canOpenAgentTabFromRoute({
|
||||
agentId: "archived-agent",
|
||||
agentsHydrated: true,
|
||||
knownAgentIds,
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not prune archived agent tabs when knownAgentIds contains the agent", () => {
|
||||
const knownAgentIds = new Set(["archived-agent"]);
|
||||
|
||||
|
||||
@@ -57,20 +57,6 @@ function setsEqual(a: Set<string>, b: Set<string>): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export function canOpenAgentTabFromRoute(input: {
|
||||
agentId: string;
|
||||
agentsHydrated: boolean;
|
||||
knownAgentIds: Set<string>;
|
||||
}): boolean {
|
||||
if (!input.agentId.trim()) {
|
||||
return false;
|
||||
}
|
||||
if (!input.agentsHydrated) {
|
||||
return true;
|
||||
}
|
||||
return input.knownAgentIds.has(input.agentId);
|
||||
}
|
||||
|
||||
export function shouldPruneWorkspaceAgentTab(input: {
|
||||
agentId: string;
|
||||
agentsHydrated: boolean;
|
||||
|
||||
@@ -64,8 +64,6 @@ import { useKeyboardActionHandler } from "@/hooks/use-keyboard-action-handler";
|
||||
import type { KeyboardActionDefinition } from "@/keyboard/keyboard-action-dispatcher";
|
||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||
import {
|
||||
buildWorkspaceOpenIntentParam,
|
||||
type WorkspaceOpenIntent,
|
||||
decodeWorkspaceIdFromPathSegment,
|
||||
} from "@/utils/host-routes";
|
||||
import { normalizeWorkspaceIdentity } from "@/utils/workspace-identity";
|
||||
@@ -103,7 +101,6 @@ import {
|
||||
} from "@/screens/workspace/workspace-agent-visibility";
|
||||
import {
|
||||
deriveWorkspacePaneState,
|
||||
type WorkspaceDerivedTab,
|
||||
} from "@/screens/workspace/workspace-pane-state";
|
||||
import {
|
||||
buildWorkspacePaneContentModel,
|
||||
@@ -125,7 +122,6 @@ const EMPTY_PINNED_AGENT_IDS = new Set<string>();
|
||||
type WorkspaceScreenProps = {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
openIntent?: WorkspaceOpenIntent | null;
|
||||
};
|
||||
|
||||
function trimNonEmpty(value: string | null | undefined): string | null {
|
||||
@@ -144,49 +140,6 @@ function decodeSegment(value: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
function buildOpenIntentKey(input: {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
openIntent?: WorkspaceOpenIntent | null;
|
||||
}): string | null {
|
||||
if (!input.openIntent) {
|
||||
return null;
|
||||
}
|
||||
const openParam = buildWorkspaceOpenIntentParam(input.openIntent);
|
||||
if (!openParam) {
|
||||
return null;
|
||||
}
|
||||
return `${input.serverId}:${input.workspaceId}:${openParam}`;
|
||||
}
|
||||
|
||||
function openIntentMatchesActiveTab(input: {
|
||||
openIntent?: WorkspaceOpenIntent | null;
|
||||
activeTab: WorkspaceDerivedTab | null;
|
||||
}): boolean {
|
||||
const { openIntent, activeTab } = input;
|
||||
if (!openIntent || !activeTab) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const target = activeTab.descriptor.target;
|
||||
if (openIntent.kind !== target.kind) {
|
||||
return false;
|
||||
}
|
||||
if (openIntent.kind === "agent" && target.kind === "agent") {
|
||||
return target.agentId === openIntent.agentId;
|
||||
}
|
||||
if (openIntent.kind === "terminal" && target.kind === "terminal") {
|
||||
return target.terminalId === openIntent.terminalId;
|
||||
}
|
||||
if (openIntent.kind === "file" && target.kind === "file") {
|
||||
return target.path === openIntent.path;
|
||||
}
|
||||
if (openIntent.kind === "draft" && target.kind === "draft") {
|
||||
return openIntent.draftId === "new" || target.draftId === openIntent.draftId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getFallbackTabOptionLabel(tab: WorkspaceTabDescriptor): string {
|
||||
if (tab.target.kind === "draft") {
|
||||
return "New Agent";
|
||||
@@ -507,7 +460,6 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
|
||||
export function WorkspaceScreen({
|
||||
serverId,
|
||||
workspaceId,
|
||||
openIntent,
|
||||
}: WorkspaceScreenProps) {
|
||||
const isFocused = useIsFocused();
|
||||
|
||||
@@ -520,7 +472,6 @@ export function WorkspaceScreen({
|
||||
<WorkspaceScreenContent
|
||||
serverId={serverId}
|
||||
workspaceId={workspaceId}
|
||||
openIntent={openIntent}
|
||||
/>
|
||||
</ExplorerSidebarAnimationProvider>
|
||||
);
|
||||
@@ -529,7 +480,6 @@ export function WorkspaceScreen({
|
||||
function WorkspaceScreenContent({
|
||||
serverId,
|
||||
workspaceId,
|
||||
openIntent,
|
||||
}: WorkspaceScreenProps) {
|
||||
const { theme } = useUnistyles();
|
||||
const insets = useSafeAreaInsets();
|
||||
@@ -811,7 +761,6 @@ function WorkspaceScreenContent({
|
||||
const openWorkspaceTab = useWorkspaceLayoutStore((state) => state.openTab);
|
||||
const focusWorkspaceTab = useWorkspaceLayoutStore((state) => state.focusTab);
|
||||
const closeWorkspaceTab = useWorkspaceLayoutStore((state) => state.closeTab);
|
||||
const pinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.pinAgent);
|
||||
const unpinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.unpinAgent);
|
||||
const retargetWorkspaceTab = useWorkspaceLayoutStore((state) => state.retargetTab);
|
||||
const splitWorkspacePane = useWorkspaceLayoutStore((state) => state.splitPane);
|
||||
@@ -826,33 +775,7 @@ function WorkspaceScreenContent({
|
||||
: EMPTY_PINNED_AGENT_IDS
|
||||
);
|
||||
const pendingByDraftId = useCreateFlowStore((state) => state.pendingByDraftId);
|
||||
const consumedOpenIntentsRef = useRef(new Set<string>());
|
||||
const pendingCloseTabIdsRef = useRef(new Set<string>());
|
||||
const [resolvedOpenIntentKey, setResolvedOpenIntentKey] = useState<string | null>(null);
|
||||
const currentOpenIntentKey = useMemo(
|
||||
() =>
|
||||
buildOpenIntentKey({
|
||||
serverId: normalizedServerId,
|
||||
workspaceId: normalizedWorkspaceId,
|
||||
openIntent,
|
||||
}),
|
||||
[normalizedServerId, normalizedWorkspaceId, openIntent]
|
||||
);
|
||||
const currentOpenIntentPinnedAgentId = useMemo(
|
||||
function getCurrentOpenIntentPinnedAgentId() {
|
||||
if (!openIntent || openIntent.kind !== "agent" || !hasHydratedAgents) {
|
||||
return null;
|
||||
}
|
||||
if (!workspaceAgentVisibility.knownAgentIds.has(openIntent.agentId)) {
|
||||
return null;
|
||||
}
|
||||
if (workspaceAgentVisibility.activeAgentIds.has(openIntent.agentId)) {
|
||||
return null;
|
||||
}
|
||||
return openIntent.agentId;
|
||||
},
|
||||
[hasHydratedAgents, openIntent, workspaceAgentVisibility]
|
||||
);
|
||||
const closeWorkspaceTabWithCleanup = useCallback(
|
||||
function closeWorkspaceTabWithCleanup(input: {
|
||||
tabId: string;
|
||||
@@ -949,93 +872,6 @@ function WorkspaceScreenContent({
|
||||
[ensureWorkspaceTab, focusWorkspaceTab, openWorkspaceTab, persistenceKey]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!currentOpenIntentKey) {
|
||||
if (resolvedOpenIntentKey !== null) {
|
||||
setResolvedOpenIntentKey(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (resolvedOpenIntentKey === currentOpenIntentKey) {
|
||||
return;
|
||||
}
|
||||
}, [currentOpenIntentKey, resolvedOpenIntentKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!openIntent || !persistenceKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentOpenIntentKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentOpenIntentPinnedAgentId) {
|
||||
pinWorkspaceAgent(persistenceKey, currentOpenIntentPinnedAgentId);
|
||||
}
|
||||
|
||||
const intentKey = currentOpenIntentKey;
|
||||
if (consumedOpenIntentsRef.current.has(intentKey)) {
|
||||
if (resolvedOpenIntentKey !== intentKey) {
|
||||
setResolvedOpenIntentKey(intentKey);
|
||||
}
|
||||
return;
|
||||
}
|
||||
consumedOpenIntentsRef.current.add(intentKey);
|
||||
|
||||
if (openIntent.kind === "draft") {
|
||||
const draftId = openIntent.draftId.trim();
|
||||
const tabId = openWorkspaceDraftTab({
|
||||
...(draftId === "new" ? {} : { draftId }),
|
||||
focus: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const target: WorkspaceTabTarget =
|
||||
openIntent.kind === "agent"
|
||||
? { kind: "agent", agentId: openIntent.agentId }
|
||||
: openIntent.kind === "terminal"
|
||||
? { kind: "terminal", terminalId: openIntent.terminalId }
|
||||
: { kind: "file", path: openIntent.path };
|
||||
const tabId = openWorkspaceTab(persistenceKey, target);
|
||||
if (tabId) {
|
||||
focusWorkspaceTab(persistenceKey, tabId);
|
||||
}
|
||||
}, [
|
||||
currentOpenIntentPinnedAgentId,
|
||||
currentOpenIntentKey,
|
||||
focusWorkspaceTab,
|
||||
openIntent,
|
||||
openWorkspaceDraftTab,
|
||||
openWorkspaceTab,
|
||||
pinWorkspaceAgent,
|
||||
persistenceKey,
|
||||
]);
|
||||
|
||||
const unresolvedOpenIntent = currentOpenIntentKey && resolvedOpenIntentKey !== currentOpenIntentKey
|
||||
? openIntent
|
||||
: null;
|
||||
const resolvedPaneTabState = useMemo(
|
||||
() =>
|
||||
deriveWorkspacePaneState({
|
||||
layout: workspaceLayout,
|
||||
tabs: uiTabs,
|
||||
preferredTarget:
|
||||
unresolvedOpenIntent?.kind === "agent"
|
||||
? { kind: "agent", agentId: unresolvedOpenIntent.agentId }
|
||||
: unresolvedOpenIntent?.kind === "terminal"
|
||||
? { kind: "terminal", terminalId: unresolvedOpenIntent.terminalId }
|
||||
: unresolvedOpenIntent?.kind === "draft"
|
||||
? { kind: "draft", draftId: unresolvedOpenIntent.draftId }
|
||||
: unresolvedOpenIntent?.kind === "file"
|
||||
? { kind: "file", path: unresolvedOpenIntent.path }
|
||||
: null,
|
||||
}),
|
||||
[uiTabs, unresolvedOpenIntent, workspaceLayout]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!normalizedServerId || !normalizedWorkspaceId || !persistenceKey) {
|
||||
return;
|
||||
@@ -1077,12 +913,10 @@ function WorkspaceScreenContent({
|
||||
canPruneAgentTabs &&
|
||||
tab.target.kind === "agent" &&
|
||||
!pinnedAgentIds.has(tab.target.agentId) &&
|
||||
tab.target.agentId !== currentOpenIntentPinnedAgentId &&
|
||||
shouldPruneWorkspaceAgentTab({
|
||||
agentId: tab.target.agentId,
|
||||
agentsHydrated: hasHydratedAgents,
|
||||
knownAgentIds: workspaceAgentVisibility.knownAgentIds,
|
||||
activeAgentIds: workspaceAgentVisibility.activeAgentIds,
|
||||
})
|
||||
) {
|
||||
closeWorkspaceTabWithCleanup({ tabId: tab.tabId, target: tab.target });
|
||||
@@ -1097,7 +931,6 @@ function WorkspaceScreenContent({
|
||||
}
|
||||
}, [
|
||||
closeWorkspaceTabWithCleanup,
|
||||
currentOpenIntentPinnedAgentId,
|
||||
ensureWorkspaceTab,
|
||||
hasHydratedAgents,
|
||||
pendingByDraftId,
|
||||
@@ -1109,25 +942,8 @@ function WorkspaceScreenContent({
|
||||
workspaceAgentVisibility,
|
||||
]);
|
||||
|
||||
const activeTabId = resolvedPaneTabState.activeTabId;
|
||||
const activeTab = resolvedPaneTabState.activeTab;
|
||||
|
||||
useEffect(() => {
|
||||
if (!openIntent || !currentOpenIntentKey) {
|
||||
return;
|
||||
}
|
||||
if (resolvedOpenIntentKey === currentOpenIntentKey) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
openIntentMatchesActiveTab({
|
||||
openIntent,
|
||||
activeTab,
|
||||
})
|
||||
) {
|
||||
setResolvedOpenIntentKey(currentOpenIntentKey);
|
||||
}
|
||||
}, [activeTab, currentOpenIntentKey, openIntent, resolvedOpenIntentKey]);
|
||||
const activeTabId = focusedPaneTabState.activeTabId;
|
||||
const activeTab = focusedPaneTabState.activeTab;
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeTabId || !persistenceKey) {
|
||||
@@ -1137,8 +953,8 @@ function WorkspaceScreenContent({
|
||||
}, [activeTabId, focusWorkspaceTab, persistenceKey]);
|
||||
|
||||
const tabs = useMemo<WorkspaceTabDescriptor[]>(
|
||||
() => resolvedPaneTabState.tabs.map((tab) => tab.descriptor),
|
||||
[resolvedPaneTabState.tabs]
|
||||
() => focusedPaneTabState.tabs.map((tab) => tab.descriptor),
|
||||
[focusedPaneTabState.tabs]
|
||||
);
|
||||
|
||||
const navigateToTabId = useCallback(
|
||||
@@ -1156,10 +972,6 @@ function WorkspaceScreenContent({
|
||||
if (!persistenceKey) {
|
||||
return;
|
||||
}
|
||||
if (openIntent) {
|
||||
emptyWorkspaceSeedRef.current = null;
|
||||
return;
|
||||
}
|
||||
if (workspaceAgentVisibility.activeAgentIds.size > 0 || terminals.length > 0) {
|
||||
emptyWorkspaceSeedRef.current = null;
|
||||
return;
|
||||
@@ -1177,7 +989,6 @@ function WorkspaceScreenContent({
|
||||
}, [
|
||||
normalizedServerId,
|
||||
normalizedWorkspaceId,
|
||||
openIntent,
|
||||
openWorkspaceDraftTab,
|
||||
persistenceKey,
|
||||
terminals.length,
|
||||
|
||||
Reference in New Issue
Block a user