Make New Workspace an app-wide screen (#1746)
* fix(app): make new workspace global New Workspace now opens as an app-wide screen. Host and project context can still preselect the form through query params, but the route itself no longer lives under a host. * fix(app): simplify new workspace selection
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
buildHostAgentDetailRoute,
|
||||
buildHostNewWorkspaceRoute,
|
||||
buildHostRootRoute,
|
||||
buildHostWorkspaceOpenRoute,
|
||||
buildHostWorkspaceRoute,
|
||||
buildNewWorkspaceRoute,
|
||||
buildOpenProjectRoute,
|
||||
resolveKnownHostRoute,
|
||||
buildSessionsRoute,
|
||||
@@ -149,19 +149,6 @@ describe("workspace route parsing", () => {
|
||||
).toBe("/h/local/workspace/b64_L3RtcC9yZXBv");
|
||||
});
|
||||
|
||||
it("builds a global new workspace route without a source directory", () => {
|
||||
expect(buildHostNewWorkspaceRoute("local")).toBe("/h/local/new");
|
||||
});
|
||||
|
||||
it("builds a project shortcut new workspace route with initial project context", () => {
|
||||
expect(
|
||||
buildHostNewWorkspaceRoute("local", "/repo/project", {
|
||||
displayName: "Project",
|
||||
projectId: "project-1",
|
||||
}),
|
||||
).toBe("/h/local/new?dir=%2Frepo%2Fproject&name=Project&projectId=project-1");
|
||||
});
|
||||
|
||||
it("round-trips URL-safe IDs through encode/decode", () => {
|
||||
const ids = ["1", "40", "164", "9999", "workspace-1", "opaque_id.v2~test"];
|
||||
for (const id of ids) {
|
||||
@@ -216,6 +203,25 @@ describe("global routes", () => {
|
||||
it("buildSessionsRoute returns the all-host Sessions route", () => {
|
||||
expect(buildSessionsRoute()).toBe("/sessions");
|
||||
});
|
||||
|
||||
it("buildNewWorkspaceRoute returns the all-host New Workspace route", () => {
|
||||
expect(buildNewWorkspaceRoute()).toBe("/new");
|
||||
});
|
||||
|
||||
it("buildNewWorkspaceRoute accepts an initial host", () => {
|
||||
expect(buildNewWorkspaceRoute({ serverId: "local" })).toBe("/new?serverId=local");
|
||||
});
|
||||
|
||||
it("buildNewWorkspaceRoute accepts initial project context", () => {
|
||||
expect(
|
||||
buildNewWorkspaceRoute({
|
||||
serverId: "local",
|
||||
sourceDirectory: "/repo/project",
|
||||
displayName: "Project",
|
||||
projectId: "project-1",
|
||||
}),
|
||||
).toBe("/new?serverId=local&dir=%2Frepo%2Fproject&name=Project&projectId=project-1");
|
||||
});
|
||||
});
|
||||
|
||||
describe("host settings section slugs", () => {
|
||||
|
||||
@@ -414,6 +414,39 @@ export function buildOpenProjectRoute() {
|
||||
return "/open-project" as const;
|
||||
}
|
||||
|
||||
interface NewWorkspaceRouteOptions {
|
||||
serverId?: string;
|
||||
sourceDirectory?: string;
|
||||
displayName?: string;
|
||||
projectId?: string;
|
||||
}
|
||||
|
||||
function buildNewWorkspaceSearch(options: NewWorkspaceRouteOptions): string {
|
||||
const params = new URLSearchParams();
|
||||
const serverId = trimNonEmpty(options.serverId);
|
||||
if (serverId) {
|
||||
params.set("serverId", serverId);
|
||||
}
|
||||
if (options.sourceDirectory) {
|
||||
params.set("dir", options.sourceDirectory);
|
||||
}
|
||||
if (options.displayName) {
|
||||
params.set("name", options.displayName);
|
||||
}
|
||||
if (options.projectId) {
|
||||
params.set("projectId", options.projectId);
|
||||
}
|
||||
return params.toString();
|
||||
}
|
||||
|
||||
export function buildNewWorkspaceRoute(options: NewWorkspaceRouteOptions = {}) {
|
||||
const query = buildNewWorkspaceSearch(options);
|
||||
if (!query) {
|
||||
return "/new" as const;
|
||||
}
|
||||
return `/new?${query}` as const;
|
||||
}
|
||||
|
||||
export type KnownHostRouteResolution =
|
||||
| { kind: "render" }
|
||||
| { kind: "redirect"; href: ReturnType<typeof buildOpenProjectRoute> | "/welcome" };
|
||||
@@ -434,32 +467,6 @@ export function resolveKnownHostRoute(input: {
|
||||
return { kind: "redirect", href: "/welcome" };
|
||||
}
|
||||
|
||||
export function buildHostNewWorkspaceRoute(
|
||||
serverId: string,
|
||||
sourceDirectory?: string,
|
||||
options?: { displayName?: string; projectId?: string },
|
||||
) {
|
||||
const base = buildHostRootRoute(serverId);
|
||||
if (base === "/") {
|
||||
return "/" as const;
|
||||
}
|
||||
const params = new URLSearchParams();
|
||||
if (sourceDirectory) {
|
||||
params.set("dir", sourceDirectory);
|
||||
}
|
||||
if (options?.displayName) {
|
||||
params.set("name", options.displayName);
|
||||
}
|
||||
if (options?.projectId) {
|
||||
params.set("projectId", options.projectId);
|
||||
}
|
||||
const query = params.toString();
|
||||
if (!query) {
|
||||
return `${base}/new` as const;
|
||||
}
|
||||
return `${base}/new?${query}` as const;
|
||||
}
|
||||
|
||||
export const SETTINGS_SECTION_SLUGS = [
|
||||
"general",
|
||||
"daemon",
|
||||
|
||||
@@ -40,7 +40,7 @@ describe("buildWorkspaceArchiveRedirectRoute", () => {
|
||||
archivedWorkspaceId: "/repo/.paseo/worktrees/feature",
|
||||
workspaces,
|
||||
}),
|
||||
).toBe("/h/server-1/new?dir=%2Frepo&name=Project&projectId=project-1");
|
||||
).toBe("/new?serverId=server-1&dir=%2Frepo&name=Project&projectId=project-1");
|
||||
});
|
||||
|
||||
it("redirects to the new workspace route when no sibling workspace target exists", () => {
|
||||
@@ -58,7 +58,7 @@ describe("buildWorkspaceArchiveRedirectRoute", () => {
|
||||
archivedWorkspaceId: "/repo/.paseo/worktrees/feature",
|
||||
workspaces,
|
||||
}),
|
||||
).toBe("/h/server-1/new?dir=%2Frepo&name=Project&projectId=project-1");
|
||||
).toBe("/new?serverId=server-1&dir=%2Frepo&name=Project&projectId=project-1");
|
||||
});
|
||||
|
||||
it("redirects to the new workspace route instead of another workspace", () => {
|
||||
@@ -78,7 +78,7 @@ describe("buildWorkspaceArchiveRedirectRoute", () => {
|
||||
archivedWorkspaceId: "/notes",
|
||||
workspaces,
|
||||
}),
|
||||
).toBe("/h/server-1/new?dir=%2Fnotes&name=Project&projectId=notes");
|
||||
).toBe("/new?serverId=server-1&dir=%2Fnotes&name=Project&projectId=notes");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,6 +136,6 @@ describe("redirectIfArchivingActiveWorkspace", () => {
|
||||
),
|
||||
).toBe(true);
|
||||
|
||||
expect(routes).toEqual(["/h/server-1/new?dir=%2Frepo&name=Project&projectId=project-1"]);
|
||||
expect(routes).toEqual(["/new?serverId=server-1&dir=%2Frepo&name=Project&projectId=project-1"]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Href } from "expo-router";
|
||||
import type { WorkspaceDescriptor } from "@/stores/session-store";
|
||||
import { buildHostNewWorkspaceRoute, buildHostRootRoute } from "@/utils/host-routes";
|
||||
import { buildHostRootRoute, buildNewWorkspaceRoute } from "@/utils/host-routes";
|
||||
import { resolveWorkspaceRouteId } from "@/utils/workspace-identity";
|
||||
|
||||
export function buildWorkspaceArchiveRedirectRoute(input: {
|
||||
@@ -23,7 +23,9 @@ export function buildWorkspaceArchiveRedirectRoute(input: {
|
||||
return buildHostRootRoute(input.serverId);
|
||||
}
|
||||
|
||||
return buildHostNewWorkspaceRoute(input.serverId, sourceDirectory, {
|
||||
return buildNewWorkspaceRoute({
|
||||
serverId: input.serverId,
|
||||
sourceDirectory,
|
||||
displayName: archivedWorkspace.projectDisplayName,
|
||||
projectId: archivedWorkspace.projectId,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user