Fix optimistic workspace creation on Android
This commit is contained in:
@@ -112,10 +112,18 @@ describe("navigation active workspace store", () => {
|
||||
serverId: "server-1",
|
||||
workspaceId: "workspace-a",
|
||||
});
|
||||
expect(store.getLastNavigationWorkspaceRouteSelection()).toEqual({
|
||||
serverId: "server-1",
|
||||
workspaceId: "workspace-a",
|
||||
});
|
||||
|
||||
store.syncNavigationActiveWorkspace(createNavigationPathRef("/h/server-1/sessions"));
|
||||
|
||||
expect(store.getNavigationActiveWorkspaceSelection()).toBeNull();
|
||||
expect(store.getLastNavigationWorkspaceRouteSelection()).toEqual({
|
||||
serverId: "server-1",
|
||||
workspaceId: "workspace-a",
|
||||
});
|
||||
});
|
||||
|
||||
it("clears stale workspace params when navigation sync reports a non-workspace path", async () => {
|
||||
@@ -134,4 +142,35 @@ describe("navigation active workspace store", () => {
|
||||
|
||||
expect(store.getNavigationActiveWorkspaceSelection()).toBeNull();
|
||||
});
|
||||
|
||||
it("uses a one-shot workspace route override when returning to a retained shell", async () => {
|
||||
installWindowStub("/h/server-1/workspace/workspace-a");
|
||||
const store = await import("@/stores/navigation-active-workspace-store");
|
||||
|
||||
store.syncNavigationActiveWorkspace(
|
||||
createNavigationPathRef("/h/server-1/workspace/workspace-a"),
|
||||
);
|
||||
store.overrideNextNavigationWorkspaceRouteSelection({
|
||||
serverId: "server-1",
|
||||
workspaceId: "workspace-b",
|
||||
});
|
||||
|
||||
store.syncNavigationActiveWorkspace(
|
||||
createNavigationPathRef("/h/server-1/workspace/workspace-a"),
|
||||
);
|
||||
|
||||
expect(store.getNavigationActiveWorkspaceSelection()).toEqual({
|
||||
serverId: "server-1",
|
||||
workspaceId: "workspace-b",
|
||||
});
|
||||
|
||||
store.syncNavigationActiveWorkspace(
|
||||
createNavigationPathRef("/h/server-1/workspace/workspace-a"),
|
||||
);
|
||||
|
||||
expect(store.getNavigationActiveWorkspaceSelection()).toEqual({
|
||||
serverId: "server-1",
|
||||
workspaceId: "workspace-a",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -39,6 +39,8 @@ interface NavigationObserverRef {
|
||||
}
|
||||
|
||||
let snapshot: ActiveWorkspaceSelection | null = null;
|
||||
let lastWorkspaceRouteSelection: ActiveWorkspaceSelection | null = null;
|
||||
let nextWorkspaceRouteSelectionOverride: ActiveWorkspaceSelection | null = null;
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
function subscribe(listener: () => void): () => void {
|
||||
@@ -165,7 +167,18 @@ function getActiveWorkspaceForNavigationSync(
|
||||
}
|
||||
|
||||
export function syncNavigationActiveWorkspace(navigationRef: NavigationObserverRef) {
|
||||
emitIfChanged(getActiveWorkspaceForNavigationSync(navigationRef.current?.getCurrentRoute()));
|
||||
const route = navigationRef.current?.getCurrentRoute();
|
||||
const routeState = classifyNavigationWorkspaceRoute(route);
|
||||
if (routeState.kind === "workspace") {
|
||||
lastWorkspaceRouteSelection = routeState.selection;
|
||||
if (nextWorkspaceRouteSelectionOverride) {
|
||||
const overrideSelection = nextWorkspaceRouteSelectionOverride;
|
||||
nextWorkspaceRouteSelectionOverride = null;
|
||||
emitIfChanged(overrideSelection);
|
||||
return;
|
||||
}
|
||||
}
|
||||
emitIfChanged(getActiveWorkspaceForNavigationSync(route));
|
||||
}
|
||||
|
||||
export function activateNavigationWorkspaceSelection(
|
||||
@@ -180,6 +193,14 @@ export function getNavigationActiveWorkspaceSelection(): ActiveWorkspaceSelectio
|
||||
return getSnapshot();
|
||||
}
|
||||
|
||||
export function getLastNavigationWorkspaceRouteSelection(): ActiveWorkspaceSelection | null {
|
||||
return lastWorkspaceRouteSelection;
|
||||
}
|
||||
|
||||
export function overrideNextNavigationWorkspaceRouteSelection(next: ActiveWorkspaceSelection) {
|
||||
nextWorkspaceRouteSelectionOverride = next;
|
||||
}
|
||||
|
||||
export function syncBrowserActiveWorkspaceFromLocation() {
|
||||
emitIfChanged(getBrowserLocationWorkspace());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { create } from "zustand";
|
||||
import type { ComposerAttachment } from "@/attachments/types";
|
||||
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||
|
||||
export interface PendingWorkspaceDraftSubmission {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
draftId: string;
|
||||
text: string;
|
||||
attachments: ComposerAttachment[];
|
||||
cwd: string;
|
||||
provider: AgentProvider;
|
||||
modeId?: string;
|
||||
model?: string;
|
||||
thinkingOptionId?: string;
|
||||
featureValues?: Record<string, unknown>;
|
||||
allowEmptyText?: boolean;
|
||||
}
|
||||
|
||||
type WorkspaceDraftSubmissionState = {
|
||||
pendingByDraftId: Record<string, PendingWorkspaceDraftSubmission>;
|
||||
setPending: (submission: PendingWorkspaceDraftSubmission) => void;
|
||||
consumePending: (input: {
|
||||
serverId: string;
|
||||
workspaceId: string;
|
||||
draftId: string;
|
||||
}) => PendingWorkspaceDraftSubmission | null;
|
||||
};
|
||||
|
||||
function matchesPendingSubmission(
|
||||
pending: PendingWorkspaceDraftSubmission | null | undefined,
|
||||
input: { serverId: string; workspaceId: string; draftId: string },
|
||||
): pending is PendingWorkspaceDraftSubmission {
|
||||
return (
|
||||
pending?.serverId === input.serverId &&
|
||||
pending.workspaceId === input.workspaceId &&
|
||||
pending.draftId === input.draftId
|
||||
);
|
||||
}
|
||||
|
||||
export const useWorkspaceDraftSubmissionStore = create<WorkspaceDraftSubmissionState>(
|
||||
(set, get) => ({
|
||||
pendingByDraftId: {},
|
||||
setPending: (submission) =>
|
||||
set((state) => ({
|
||||
pendingByDraftId: {
|
||||
...state.pendingByDraftId,
|
||||
[submission.draftId]: submission,
|
||||
},
|
||||
})),
|
||||
consumePending: (input) => {
|
||||
const pending = get().pendingByDraftId[input.draftId];
|
||||
if (!matchesPendingSubmission(pending, input)) {
|
||||
return null;
|
||||
}
|
||||
set((state) => {
|
||||
if (!matchesPendingSubmission(state.pendingByDraftId[input.draftId], input)) {
|
||||
return state;
|
||||
}
|
||||
const { [input.draftId]: _removed, ...rest } = state.pendingByDraftId;
|
||||
return { pendingByDraftId: rest };
|
||||
});
|
||||
return pending;
|
||||
},
|
||||
}),
|
||||
);
|
||||
Reference in New Issue
Block a user