Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions apps/web/src/global-routines.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { describeApiError } from "@/lib/api-query";
import type { APIQuery } from "@/lib/api-query";

import { useBench } from "./bench-context";
import { useFromBench } from "./shell/page-crumbs";
import { WORKFLOWS_PATH_PREFIX } from "./path-ids";
import { tenantKeys } from "./query-client";
import {
Expand All @@ -30,7 +31,7 @@ export function routineDetailPath(definitionId: string): string {
return `${WORKFLOWS_PATH_PREFIX}/${encodeURIComponent(definitionId)}`;
}

function useMemberBenches(): {
function useMemberBenches(fromBench: string | null): {
readonly kind: "loading" | "ready";
readonly benches: readonly { tenantId: string; tenantName: string }[];
} {
Expand All @@ -42,12 +43,15 @@ function useMemberBenches(): {
() => benchMemberships.map((m) => ({ tenantId: m.tenantId, tenantName: m.tenantName })),
[benchMemberships],
);
if (fromBench !== null) {
return { kind: "ready", benches: [{ tenantId: fromBench, tenantName: "" }] };
}
if (memberships.kind !== "ready") return { kind: "loading", benches: [] };
return { kind: "ready", benches };
}

export function useGlobalRoutines(): APIQuery<readonly GlobalRoutineRow[]> {
const { kind: benchesKind, benches } = useMemberBenches();
const { kind: benchesKind, benches } = useMemberBenches(useFromBench());
const results = useQueries({
queries: benches.map((bench) => ({
queryKey: [...tenantKeys.routines(bench.tenantId), ROUTINES_QUERY_SCOPE],
Expand Down
13 changes: 10 additions & 3 deletions apps/web/src/pages/insights-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import { useNavigate } from "../navigation";
import { tenantKeys } from "../query-client";
import { useAPIQuery } from "../api";
import { INSIGHTS_PATH_PREFIX, INSIGHTS_RUNS_PATH } from "../path-ids";
import { benchLink, useFromBench } from "../shell/page-crumbs";
import { StageTopBar } from "../shell/stage-top-bar";
import {
listScheduledWorkflows,
Expand Down Expand Up @@ -745,7 +746,11 @@ function InsightsWorkbenchPage({
}

export function InsightsRoute({ path }: { readonly path?: string }) {
const { selectedTenantId } = useBench();
const { selectedTenantId: benchTenantId } = useBench();
// A run opened from a workbench's Insights carries `from=`, so it reads
// that workbench's tenant rather than the bench.
const fromBench = useFromBench();
const selectedTenantId = fromBench ?? benchTenantId;
const navigate = useNavigate();
const currentPath =
path ?? (typeof window !== "undefined" ? window.location.pathname : INSIGHTS_PATH_PREFIX);
Expand Down Expand Up @@ -775,8 +780,10 @@ export function InsightsRoute({ path }: { readonly path?: string }) {
return (
<InsightsWorkbenchPageRoute
workbenchId={workbenchId}
benchTenantId={selectedTenantId}
onOpenRun={(id) => navigate(`${INSIGHTS_RUNS_PATH}/${encodeURIComponent(id)}`)}
benchTenantId={benchTenantId}
onOpenRun={(id) =>
navigate(benchLink(`${INSIGHTS_RUNS_PATH}/${encodeURIComponent(id)}`, workbenchId))
}
/>
);
}
Expand Down
5 changes: 4 additions & 1 deletion apps/web/src/pages/library-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { Link } from "../navigation";
import { ARTIFACTS_PATH_PREFIX } from "../path-ids";
import { tenantKeys } from "../query-client";
import { useBenchActivity } from "../shell/bench-activity";
import { useFromBench } from "../shell/page-crumbs";
import {
artifactUploadToast,
copyArtifactLinks,
Expand Down Expand Up @@ -595,7 +596,9 @@ export function LibraryRoute({ path }: { readonly path: string }) {
// recorded one — resolved via the same activity listing other
// bench-scoped surfaces already fetch.
const activity = useBenchActivity(selectedTenantId);
const lastWorkbenchId = selectedTenantId === null ? null : readLastWorkbenchId(selectedTenantId);
const fromBench = useFromBench();
const lastWorkbenchId =
fromBench ?? (selectedTenantId === null ? null : readLastWorkbenchId(selectedTenantId));
const workbenchScope =
activity.kind === "ready"
? resolveLibraryWorkbenchScope(activity.workbenches, lastWorkbenchId)
Expand Down
8 changes: 6 additions & 2 deletions apps/web/src/pages/routines-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useGlobalRoutines, useRoutineActions } from "../global-routines";
import type { GlobalRoutineRow } from "../global-routines";
import { routineDetailPath } from "../global-routines";
import { Link } from "../navigation";
import { benchLink, useFromBench } from "../shell/page-crumbs";
import { StageTopBar } from "../shell/stage-top-bar";

export type { GlobalRoutineRow } from "../global-routines";
Expand All @@ -39,6 +40,7 @@ export function GlobalRoutinesList({
readonly onToggleEnabled: (row: GlobalRoutineRow, enabled: boolean) => void;
readonly onRunNow: (row: GlobalRoutineRow) => Promise<void>;
}) {
const fromBench = useFromBench();
if (rows.length === 0) {
return (
<RichEmptyState
Expand Down Expand Up @@ -70,12 +72,14 @@ export function GlobalRoutinesList({
<TableCell>
<span className="flex flex-col">
<Link
to={routineDetailPath(row.definition.definitionId)}
to={benchLink(routineDetailPath(row.definition.definitionId), fromBench)}
className="text-sm font-medium"
>
{row.definition.name}
</Link>
<span className="text-xs text-[var(--ui-fg-muted)]">{row.tenantName}</span>
{row.tenantName === "" ? null : (
<span className="text-xs text-[var(--ui-fg-muted)]">{row.tenantName}</span>
)}
</span>
</TableCell>
<TableCell>
Expand Down
4 changes: 3 additions & 1 deletion apps/web/src/pages/tools-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
} from "../tools/mcp-servers-query";
import { useDeployedToolPackages } from "../tools/deployed-tool-packages";
import { useBench } from "../bench-context";
import { useFromBench } from "../shell/page-crumbs";
import { StageTopBar } from "../shell/stage-top-bar";
import { ConfirmButton } from "../components/confirm-button";

Expand Down Expand Up @@ -343,6 +344,7 @@ export function ToolsPage({ tenantId }: { readonly tenantId: string | null }) {
// A thin adapter that resolves which workbench's registry is listed.
export function ToolsRoute() {
const { selectedTenantId } = useBench();
const fromBench = useFromBench();

return <ToolsPage tenantId={selectedTenantId} />;
return <ToolsPage tenantId={fromBench ?? selectedTenantId} />;
}
7 changes: 5 additions & 2 deletions apps/web/src/router-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ function canonicalPath(pathname: string): string {
const listeners = new Set<() => void>();

let currentPath = canonicalPath(window.location.pathname);
let currentSearch = window.location.search;
if (currentPath !== window.location.pathname) {
window.history.replaceState(null, "", currentPath);
}

function setPath(next: string): void {
if (next === currentPath) return;
const search = window.location.search;
if (next === currentPath && search === currentSearch) return;
currentPath = next;
currentSearch = search;
// Published on a microtask so a render-phase `navigateTo` (a forwarding
// route resolving its target) never updates a subscriber mid-render.
queueMicrotask(() => {
Expand Down Expand Up @@ -49,7 +52,7 @@ export function navigateTo(to: string): void {
// A hop to the path already showing is a no-op, which is what makes a
// render-phase `navigate` safe to run twice (StrictMode, a re-render):
// it can never stack duplicate history entries.
if (canonical === currentPath) return;
if (canonical === currentPath && url.search === currentSearch) return;
window.history.pushState(null, "", canonical === url.pathname ? to : canonical);
setPath(canonical);
}
9 changes: 8 additions & 1 deletion apps/web/src/shell/page-crumbs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,18 @@ export function benchLink(path: string, benchId: string | null): string {

export type PageCrumb = { readonly label: string; readonly href?: string };

function useFromWorkbench(): { readonly id: string; readonly name: string | null } | null {
/** The workbench tenant id named by `?from=`, or null. Full-page views scope
* their data to it instead of the last-opened workbench. */
export function useFromBench(): string | null {
// The path store re-renders this on navigation; it keeps the pathname
// only, so the query string is read fresh.
useSyncExternalStore(subscribeToPath, getPath);
const id = new URLSearchParams(window.location.search).get(FROM_PARAM);
return id === null || id === "" ? null : id;
}

function useFromWorkbench(): { readonly id: string; readonly name: string | null } | null {
const id = useFromBench();
const { selectedTenantId } = useBench();
const tenantId = selectedTenantId ?? "";
const workbenches = useQuery({
Expand Down
Loading