diff --git a/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js b/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js
index 28fb66c012..c240f91e88 100644
--- a/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js
+++ b/plugins/example-plugin/src/nemo_example_plugin/web/dist/index.js
@@ -3,15 +3,15 @@
import { Button as e, Flex as t, Stack as n, Text as r } from "@nvidia/foundations-react-core";
import { NavLink as i, Navigate as a, Outlet as o, Route as s, Routes as c } from "react-router";
import { RelativeTime as l, StatusBadge as u, StudioDataView as d, TableEmptyState as f, useStudioDataViewState as p } from "@nemo/common";
-import { useCallback as m } from "react";
-import { jsx as h, jsxs as g } from "react/jsx-runtime";
+import { useCallback as m, useEffect as h } from "react";
+import { jsx as g, jsxs as _ } from "react/jsx-runtime";
//#region src/paths.ts
-var _ = (e, t) => `/workspaces/${e}/plugin/example/${t}`, v = [
+var v = (e, t) => `/workspaces/${e}/plugin/example/${t}`, y = [
"overview",
"auth",
"workspace",
"shared-ui"
-], y = {
+], b = {
overview: "Overview",
auth: "Auth",
workspace: "Workspace",
@@ -19,11 +19,16 @@ var _ = (e, t) => `/workspaces/${e}/plugin/example/${t}`, v = [
};
//#endregion
//#region src/SharedUiPage.tsx
-function b({ host: e }) {
+function x({ host: e }) {
let { data: t, isPending: i, isError: a } = e.sdk.platform.useEntitiesListWorkspaces({
page: 1,
page_size: 100
- }), o = t?.data ?? [], s = p(), c = m((e) => [
+ }), o = t?.data ?? [], { set: s } = e.breadcrumbs, { workspaceId: c } = e;
+ h(() => (s([{
+ label: "Example Plugin",
+ href: v(c, "overview")
+ }, { label: "Shared UI" }]), () => s([])), [s, c]);
+ let y = p(), b = m((e) => [
e.accessor("name", {
header: "Name",
size: 240
@@ -32,42 +37,42 @@ function b({ host: e }) {
id: "status",
header: "Status",
size: 120,
- cell: ({ row: e }) => /* @__PURE__ */ h(u, { status: e.original.status ?? "ready" })
+ cell: ({ row: e }) => /* @__PURE__ */ g(u, { status: e.original.status ?? "ready" })
}),
e.display({
id: "created",
header: "Created",
size: 160,
- cell: ({ row: e }) => e.original.created_at ? /* @__PURE__ */ h(l, { datetime: e.original.created_at }) : /* @__PURE__ */ h(r, {
+ cell: ({ row: e }) => e.original.created_at ? /* @__PURE__ */ g(l, { datetime: e.original.created_at }) : /* @__PURE__ */ g(r, {
kind: "body/regular/sm",
color: "secondary",
children: "—"
})
})
], []);
- return /* @__PURE__ */ g(n, {
+ return /* @__PURE__ */ _(n, {
gap: "2",
children: [
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "label/bold/md",
children: "Shared UI"
}),
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "body/regular/sm",
color: "secondary",
children: "This table is Studio's own StudioDataView, imported from @nemo/common and resolved through Studio's import map — not a copy bundled into the plugin."
}),
- a ? /* @__PURE__ */ h(f, {
+ a ? /* @__PURE__ */ g(f, {
header: "Couldn't load workspaces",
emptyMessage: "The request failed. Try again."
- }) : !i && o.length === 0 ? /* @__PURE__ */ h(f, {
+ }) : !i && o.length === 0 ? /* @__PURE__ */ g(f, {
header: "No workspaces",
emptyMessage: "Create a workspace to see it listed here."
- }) : /* @__PURE__ */ h(n, {
+ }) : /* @__PURE__ */ g(n, {
className: "min-h-[320px]",
- children: /* @__PURE__ */ h(d, {
- dataViewState: s,
- makeColumns: c,
+ children: /* @__PURE__ */ g(d, {
+ dataViewState: y,
+ makeColumns: b,
attributes: { DataViewRoot: {
data: o,
totalCount: o.length,
@@ -80,103 +85,103 @@ function b({ host: e }) {
}
//#endregion
//#region src/Root.tsx
-function x({ host: e }) {
- return /* @__PURE__ */ h(c, { children: /* @__PURE__ */ g(s, {
- element: /* @__PURE__ */ h(S, { workspaceId: e.workspaceId }),
+function S({ host: e }) {
+ return /* @__PURE__ */ g(c, { children: /* @__PURE__ */ _(s, {
+ element: /* @__PURE__ */ g(C, { workspaceId: e.workspaceId }),
children: [
- /* @__PURE__ */ h(s, {
+ /* @__PURE__ */ g(s, {
index: !0,
- element: /* @__PURE__ */ h(a, {
- to: _(e.workspaceId, "overview"),
+ element: /* @__PURE__ */ g(a, {
+ to: v(e.workspaceId, "overview"),
replace: !0
})
}),
- /* @__PURE__ */ h(s, {
+ /* @__PURE__ */ g(s, {
path: "overview",
- element: /* @__PURE__ */ h(w, { host: e })
+ element: /* @__PURE__ */ g(T, { host: e })
}),
- /* @__PURE__ */ h(s, {
+ /* @__PURE__ */ g(s, {
path: "auth",
- element: /* @__PURE__ */ h(T, { getAccessToken: e.auth.getAccessToken })
+ element: /* @__PURE__ */ g(E, { getAccessToken: e.auth.getAccessToken })
}),
- /* @__PURE__ */ h(s, {
+ /* @__PURE__ */ g(s, {
path: "workspace",
- element: /* @__PURE__ */ h(E, { workspaceId: e.workspaceId })
+ element: /* @__PURE__ */ g(D, { workspaceId: e.workspaceId })
}),
- /* @__PURE__ */ h(s, {
+ /* @__PURE__ */ g(s, {
path: "shared-ui",
- element: /* @__PURE__ */ h(b, { host: e })
+ element: /* @__PURE__ */ g(x, { host: e })
}),
- /* @__PURE__ */ h(s, {
+ /* @__PURE__ */ g(s, {
path: "*",
- element: /* @__PURE__ */ h(D, {})
+ element: /* @__PURE__ */ g(O, {})
})
]
}) });
}
-function S({ workspaceId: e }) {
+function C({ workspaceId: e }) {
let r = ({ isActive: e }) => `px-3 py-1 rounded text-sm font-medium transition-colors ${e ? "text-primary bg-surface-hover" : "text-subtle hover:text-primary"}`;
- return /* @__PURE__ */ g(n, {
+ return /* @__PURE__ */ _(n, {
gap: "4",
className: "h-full p-4",
- children: [/* @__PURE__ */ h(t, {
+ children: [/* @__PURE__ */ g(t, {
gap: "2",
className: "border-b border-subtle pb-2",
- children: v.map((t) => /* @__PURE__ */ h(i, {
- to: _(e, t),
+ children: y.map((t) => /* @__PURE__ */ g(i, {
+ to: v(e, t),
className: r,
- children: y[t]
+ children: b[t]
}, t))
- }), /* @__PURE__ */ h("div", {
+ }), /* @__PURE__ */ g("div", {
className: "flex-1",
- children: /* @__PURE__ */ h(o, {})
+ children: /* @__PURE__ */ g(o, {})
})]
});
}
-function C({ children: e }) {
- return /* @__PURE__ */ h("pre", {
+function w({ children: e }) {
+ return /* @__PURE__ */ g("pre", {
className: "bg-surface-sunken text-subtle rounded p-3 text-xs overflow-x-auto font-mono",
children: e
});
}
-function w({ host: i }) {
+function T({ host: i }) {
let { data: a, isPending: o, isError: s } = i.sdk.platform.useEntitiesListWorkspaces({
page: 1,
page_size: 100
}, { query: { staleTime: 5e3 } }), c = a?.data ?? [];
- return /* @__PURE__ */ g(n, {
+ return /* @__PURE__ */ _(n, {
gap: "2",
children: [
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "label/bold/lg",
children: "Example Plugin"
}),
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "body/regular/sm",
color: "secondary",
children: "This is an example Studio plugin. Use the tabs above or the Studio side nav to explore what information is available to a plugin at runtime."
}),
- /* @__PURE__ */ g(n, {
+ /* @__PURE__ */ _(n, {
gap: "1",
children: [
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "label/bold/sm",
children: "Shared SDK"
}),
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "body/regular/xs",
color: "secondary",
children: "Listed via Studio's sdk.platform.useEntitiesListWorkspaces() — the platform's typed hook, running on Studio's authenticated axios and shared QueryClient rather than a plugin copy."
}),
- o ? /* @__PURE__ */ h(r, {
+ o ? /* @__PURE__ */ g(r, {
kind: "body/regular/xs",
color: "secondary",
children: "Loading…"
- }) : s ? /* @__PURE__ */ h(r, {
+ }) : s ? /* @__PURE__ */ g(r, {
kind: "body/regular/xs",
color: "danger",
children: "Request failed."
- }) : /* @__PURE__ */ g(r, {
+ }) : /* @__PURE__ */ _(r, {
kind: "body/regular/sm",
children: [
c.length,
@@ -187,27 +192,27 @@ function w({ host: i }) {
})
]
}),
- /* @__PURE__ */ g(n, {
+ /* @__PURE__ */ _(n, {
gap: "1",
children: [
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "label/bold/sm",
children: "Host capabilities"
}),
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "body/regular/xs",
color: "secondary",
children: "Studio's notifications, telemetry, and navigation, all off the host handle — no plugin-side setup."
}),
- /* @__PURE__ */ g(t, {
+ /* @__PURE__ */ _(t, {
gap: "2",
- children: [/* @__PURE__ */ h(e, {
+ children: [/* @__PURE__ */ g(e, {
kind: "secondary",
onClick: () => {
i.notifications.notify("Toast from the example plugin", "success"), i.telemetry.event("overview_notify_clicked");
},
children: "Notify"
- }), /* @__PURE__ */ h(e, {
+ }), /* @__PURE__ */ g(e, {
kind: "secondary",
onClick: () => i.navigation.navigate(`/workspaces/${i.workspaceId}/base-models`),
children: "Go to Base Models"
@@ -218,37 +223,37 @@ function w({ host: i }) {
]
});
}
-function T({ getAccessToken: e }) {
+function E({ getAccessToken: e }) {
let t = e(), i = null;
try {
let e = t.split(".")[1];
e && (i = JSON.parse(atob(e.replace(/-/g, "+").replace(/_/g, "/"))));
} catch {}
- return /* @__PURE__ */ g(n, {
+ return /* @__PURE__ */ _(n, {
gap: "3",
children: [
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "label/bold/md",
children: "Auth"
}),
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "body/regular/sm",
color: "secondary",
children: "Studio passes an OIDC access token to every plugin via the plugin's auth prop. Call getAccessToken() per request — it returns the current token after silent renewal — and use it as a Bearer token."
}),
- /* @__PURE__ */ g(n, {
+ /* @__PURE__ */ _(n, {
gap: "1",
- children: [/* @__PURE__ */ h(r, {
+ children: [/* @__PURE__ */ g(r, {
kind: "label/bold/sm",
children: "Example API call"
- }), /* @__PURE__ */ h(C, { children: "fetch('/apis/v1/workspaces', {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })]
+ }), /* @__PURE__ */ g(w, { children: "fetch('/apis/v1/workspaces', {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })]
}),
- /* @__PURE__ */ g(n, {
+ /* @__PURE__ */ _(n, {
gap: "1",
- children: [/* @__PURE__ */ h(r, {
+ children: [/* @__PURE__ */ g(r, {
kind: "label/bold/sm",
children: "Token claims (decoded, not verified)"
- }), i ? /* @__PURE__ */ h(C, { children: JSON.stringify(i, null, 2) }) : /* @__PURE__ */ h(r, {
+ }), i ? /* @__PURE__ */ g(w, { children: JSON.stringify(i, null, 2) }) : /* @__PURE__ */ g(r, {
kind: "body/regular/xs",
color: "secondary",
children: t ? "Could not decode token." : "No token provided."
@@ -257,38 +262,38 @@ function T({ getAccessToken: e }) {
]
});
}
-function E({ workspaceId: e }) {
- return /* @__PURE__ */ g(n, {
+function D({ workspaceId: e }) {
+ return /* @__PURE__ */ _(n, {
gap: "3",
children: [
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "label/bold/md",
children: "Workspace"
}),
- /* @__PURE__ */ h(r, {
+ /* @__PURE__ */ g(r, {
kind: "body/regular/sm",
color: "secondary",
children: "Studio passes the current workspace ID to every plugin via the plugin's workspaceId prop."
}),
- /* @__PURE__ */ g(n, {
+ /* @__PURE__ */ _(n, {
gap: "1",
- children: [/* @__PURE__ */ h(r, {
+ children: [/* @__PURE__ */ g(r, {
kind: "label/bold/sm",
children: "Current workspace"
- }), /* @__PURE__ */ h(C, { children: e })]
+ }), /* @__PURE__ */ g(w, { children: e })]
}),
- /* @__PURE__ */ g(n, {
+ /* @__PURE__ */ _(n, {
gap: "1",
- children: [/* @__PURE__ */ h(r, {
+ children: [/* @__PURE__ */ g(r, {
kind: "label/bold/sm",
children: "Example API call scoped to this workspace"
- }), /* @__PURE__ */ h(C, { children: "fetch(`/apis/v1/workspaces/${workspaceId}/models`, {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })]
+ }), /* @__PURE__ */ g(w, { children: "fetch(`/apis/v1/workspaces/${workspaceId}/models`, {\n headers: { Authorization: `Bearer ${getAccessToken()}` },\n})" })]
})
]
});
}
-function D() {
- return /* @__PURE__ */ h(r, {
+function O() {
+ return /* @__PURE__ */ g(r, {
kind: "body/regular/sm",
color: "secondary",
children: "Page not found."
@@ -296,34 +301,34 @@ function D() {
}
//#endregion
//#region src/Nav.tsx
-var O = (e) => [{
+var k = (e) => [{
group: "Example Plugin",
items: [
{
id: "example-overview",
iconName: "flask-conical",
label: "Overview",
- href: _(e, "overview")
+ href: v(e, "overview")
},
{
id: "example-auth",
iconName: "key-round",
label: "Auth",
- href: _(e, "auth")
+ href: v(e, "auth")
},
{
id: "example-workspace",
iconName: "building-2",
label: "Workspace",
- href: _(e, "workspace")
+ href: v(e, "workspace")
},
{
id: "example-shared-ui",
iconName: "table",
label: "Shared UI",
- href: _(e, "shared-ui")
+ href: v(e, "shared-ui")
}
]
}];
//#endregion
-export { x as Root, O as navItems };
+export { S as Root, k as navItems };
diff --git a/plugins/example-plugin/web/AGENTS.md b/plugins/example-plugin/web/AGENTS.md
index f401cf2f31..24c75a6078 100644
--- a/plugins/example-plugin/web/AGENTS.md
+++ b/plugins/example-plugin/web/AGENTS.md
@@ -31,11 +31,15 @@ Runtime contract: `../../../web/packages/studio/src/plugins/types.ts`.
Studio injects everything a plugin needs through a **single `host` prop**
(`host.workspaceId`, `host.auth`, `host.sdk`, `host.navigation`,
-`host.notifications`, `host.telemetry`) — grouped so new capabilities extend the
+`host.notifications`, `host.telemetry`, `host.breadcrumbs`) — grouped so new capabilities extend the
handle without changing `Root`'s signature. Destructure what you use. All are
backed by Studio's own singletons: `notifications` fires into Studio's shared
toaster, `telemetry` logs to Studio's OTEL pipeline (auto-scoped to the plugin),
-`navigation` drives Studio's shared router.
+`navigation` drives Studio's shared router, and `breadcrumbs` writes Studio's
+breadcrumb bar — which lives in GlobalNav, *outside* the plugin's subtree, so a
+plugin cannot render it itself. Studio clears the trail when the plugin
+unmounts, but **not between pages within the plugin**: return a cleanup that
+clears it, or the trail follows you to the next page. See `src/SharedUiPage.tsx`.
`@tanstack/react-query` **is** shared — call `useQuery`/`useMutation` and it reads
Studio's `QueryClientProvider` (one cache across Studio and every plugin). Put the
@@ -81,6 +85,16 @@ import { StudioDataView, useStudioDataViewState } from '@nemo/common';
Studio bundles the same files through its own graph. A plugin adds no CSS.
- **`useStudioDataViewState` syncs to URL search params** on Studio's shared
router — two DataViews on one route will fight over them.
+- **Toasts need `onNotify`.** `ToastProvider` is *not* shared: Studio mounts it
+ by deep import, so this bundle carries its own `ToastContext` with nothing in
+ it. `ConfirmationModal`, `DeleteConfirmationModal` and `LogViewer` therefore
+ take an `onNotify` prop — pass `host.notifications.notify` and the message
+ lands in Studio's toaster. Omit it and the message is dropped with a
+ `logger.warn`; nothing throws, so the miss is silent in the UI.
+
+ ```tsx
+
+ ```
## Contract
@@ -92,8 +106,15 @@ import { StudioDataView, useStudioDataViewState } from '@nemo/common';
auth: { accessToken: string; getAccessToken: () => string };
sdk: { platform: /* @nemo/sdk platform hooks */ };
navigation: { navigate: (to: string) => void; back: () => void };
- notifications: { notify: (message: string, type?: 'success'|'error'|'info'|'warning') => void };
+ notifications: {
+ notify: (
+ message: string,
+ type?: 'success'|'error'|'info'|'warning',
+ options?: { durationMs?: number | false },
+ ) => void;
+ };
telemetry: { info; warn; error: (m, cause?) => void; event: (name, attrs?) => void };
+ breadcrumbs: { set: (trail: { label: string; href?: string }[]) => void };
};
}
```
diff --git a/plugins/example-plugin/web/src/SharedUiPage.tsx b/plugins/example-plugin/web/src/SharedUiPage.tsx
index ea71cb18e3..3907e5420d 100644
--- a/plugins/example-plugin/web/src/SharedUiPage.tsx
+++ b/plugins/example-plugin/web/src/SharedUiPage.tsx
@@ -9,7 +9,8 @@ import {
useStudioDataViewState,
} from "@nemo/common";
import { Stack, Text } from "@nvidia/foundations-react-core";
-import { useCallback, type ComponentProps } from "react";
+import { useCallback, useEffect, type ComponentProps } from "react";
+import { pluginPath } from "./paths";
import type { PluginHost, Workspace } from "./types";
// `@nemo/common` is external like react — this is Studio's own StudioDataView,
@@ -22,6 +23,18 @@ export function SharedUiPage({ host }: { host: PluginHost }) {
});
const workspaces = data?.data ?? [];
+ // Renders in Studio's chrome, outside this subtree. Studio clears the trail
+ // when the plugin unmounts, but not between pages — so clear it here too.
+ const { set: setBreadcrumbs } = host.breadcrumbs;
+ const { workspaceId } = host;
+ useEffect(() => {
+ setBreadcrumbs([
+ { label: "Example Plugin", href: pluginPath(workspaceId, "overview") },
+ { label: "Shared UI" },
+ ]);
+ return () => setBreadcrumbs([]);
+ }, [setBreadcrumbs, workspaceId]);
+
// Syncs to URL search params — one DataView per route or they fight.
const dataViewState = useStudioDataViewState();
diff --git a/plugins/example-plugin/web/src/types.ts b/plugins/example-plugin/web/src/types.ts
index 2efe153219..f6955147e8 100644
--- a/plugins/example-plugin/web/src/types.ts
+++ b/plugins/example-plugin/web/src/types.ts
@@ -35,8 +35,21 @@ export interface PluginNavigation {
export type NotificationType = 'success' | 'error' | 'info' | 'warning';
+export interface NotificationOptions {
+ durationMs?: number | false;
+}
+
export interface PluginNotifications {
- notify: (message: string, type?: NotificationType) => void;
+ notify: (message: string, type?: NotificationType, options?: NotificationOptions) => void;
+}
+
+export interface PluginBreadcrumb {
+ label: string;
+ href?: string;
+}
+
+export interface PluginBreadcrumbs {
+ set: (trail: PluginBreadcrumb[]) => void;
}
export interface PluginTelemetry {
@@ -56,6 +69,7 @@ export interface PluginHost {
navigation: PluginNavigation;
notifications: PluginNotifications;
telemetry: PluginTelemetry;
+ breadcrumbs: PluginBreadcrumbs;
}
export interface PluginRootProps {
diff --git a/web/packages/common/package.json b/web/packages/common/package.json
index 4b1aa7d21c..bfa768d37b 100644
--- a/web/packages/common/package.json
+++ b/web/packages/common/package.json
@@ -53,6 +53,7 @@
"happy-dom": "catalog:",
"msw": "catalog:",
"p-limit": "catalog:",
+ "react-dropzone": "catalog:",
"react-hook-form": "catalog:",
"tsx": "catalog:",
"typescript": "catalog:",
@@ -77,6 +78,7 @@
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
+ "@opentelemetry/api-logs": "^0.219.0",
"@tanstack/match-sorter-utils": "^8.19.4",
"@tanstack/react-table": "8.20.5",
"@tanstack/react-virtual": "3.13.6",
diff --git a/web/packages/studio/src/api/common/utils.test.ts b/web/packages/common/src/api/common/utils.test.ts
similarity index 98%
rename from web/packages/studio/src/api/common/utils.test.ts
rename to web/packages/common/src/api/common/utils.test.ts
index 3c04d54feb..60179799cb 100644
--- a/web/packages/studio/src/api/common/utils.test.ts
+++ b/web/packages/common/src/api/common/utils.test.ts
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { getErrorMessage, isValidationErrorArray } from '@studio/api/common/utils';
+import { getErrorMessage, isValidationErrorArray } from '@nemo/common/src/api/common/utils';
import { AxiosError, AxiosHeaders, InternalAxiosRequestConfig } from 'axios';
describe('isValidationErrorArray', () => {
diff --git a/web/packages/studio/src/api/common/utils.ts b/web/packages/common/src/api/common/utils.ts
similarity index 100%
rename from web/packages/studio/src/api/common/utils.ts
rename to web/packages/common/src/api/common/utils.ts
diff --git a/web/packages/studio/src/components/AccessibleTitle/index.test.tsx b/web/packages/common/src/components/AccessibleTitle/index.test.tsx
similarity index 93%
rename from web/packages/studio/src/components/AccessibleTitle/index.test.tsx
rename to web/packages/common/src/components/AccessibleTitle/index.test.tsx
index feb91bb83d..d62531662f 100644
--- a/web/packages/studio/src/components/AccessibleTitle/index.test.tsx
+++ b/web/packages/common/src/components/AccessibleTitle/index.test.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { render, waitFor } from '@testing-library/react';
describe('AccessibleTitle', () => {
diff --git a/web/packages/studio/src/components/AccessibleTitle/index.tsx b/web/packages/common/src/components/AccessibleTitle/index.tsx
similarity index 100%
rename from web/packages/studio/src/components/AccessibleTitle/index.tsx
rename to web/packages/common/src/components/AccessibleTitle/index.tsx
diff --git a/web/packages/studio/src/components/AccordionSection/index.tsx b/web/packages/common/src/components/AccordionSection/index.tsx
similarity index 100%
rename from web/packages/studio/src/components/AccordionSection/index.tsx
rename to web/packages/common/src/components/AccordionSection/index.tsx
diff --git a/web/packages/studio/src/components/CancelJobButton/index.tsx b/web/packages/common/src/components/CancelJobButton/index.tsx
similarity index 91%
rename from web/packages/studio/src/components/CancelJobButton/index.tsx
rename to web/packages/common/src/components/CancelJobButton/index.tsx
index f86af9b302..881a5ee86c 100644
--- a/web/packages/studio/src/components/CancelJobButton/index.tsx
+++ b/web/packages/common/src/components/CancelJobButton/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { FormModal } from '@nemo/common/src/components/FormModal';
import { CJobCancellableStatuses } from '@nemo/common/src/constants/query';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
@@ -11,23 +12,26 @@ import {
} from '@nemo/sdk/generated/platform/api';
import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useQueryClient } from '@tanstack/react-query';
import { X } from 'lucide-react';
import { FC, MouseEvent, useState } from 'react';
interface CancelJobButtonProps {
+ workspace: string;
jobName: string;
jobStatus?: PlatformJobStatus;
compact?: boolean;
}
-export const CancelJobButton: FC = ({ jobName, jobStatus, compact }) => {
+export const CancelJobButton: FC = ({
+ workspace,
+ jobName,
+ jobStatus,
+ compact,
+}) => {
const [isModalOpen, setIsModalOpen] = useState(false);
const toast = useToast();
const queryClient = useQueryClient();
- const workspace = useWorkspaceFromPath();
const { mutateAsync, isPending } = useJobsCancelJob({
mutation: {
diff --git a/web/packages/studio/src/components/modals/ConfirmationModal/index.tsx b/web/packages/common/src/components/ConfirmationModal/index.tsx
similarity index 87%
rename from web/packages/studio/src/components/modals/ConfirmationModal/index.tsx
rename to web/packages/common/src/components/ConfirmationModal/index.tsx
index 41cc0c491d..252756199d 100644
--- a/web/packages/studio/src/components/modals/ConfirmationModal/index.tsx
+++ b/web/packages/common/src/components/ConfirmationModal/index.tsx
@@ -4,10 +4,11 @@
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
import { LoadingButton } from '@nemo/common/src/components/LoadingButton';
-import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import type { NotifyFn } from '@nemo/common/src/providers/toast/types';
+import { useNotify } from '@nemo/common/src/providers/toast/useNotify';
+import { getErrorMessage } from '@nemo/common/src/utils/error';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/components/NewDataDesignerJobForm/utils';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { type ComponentProps, type FC, useState } from 'react';
import { useForm } from 'react-hook-form';
@@ -34,6 +35,8 @@ export interface ConfirmationModalProps extends Pick = ({
@@ -49,6 +52,7 @@ export const ConfirmationModal: FC = ({
submitButtonText = 'Confirm',
submitButtonColor,
suppressResultToasts = false,
+ onNotify,
}) => {
const {
reset,
@@ -60,7 +64,7 @@ export const ConfirmationModal: FC = ({
defaultValues: { confirmText: '' },
});
const [isPending, setIsPending] = useState(false);
- const toast = useToast();
+ const notify = useNotify(onNotify);
const resetAndClose = () => {
reset();
@@ -73,13 +77,13 @@ export const ConfirmationModal: FC = ({
const ok = await onConfirm();
if (!suppressResultToasts) {
if (ok) {
- toast.success(successText);
+ notify(successText, 'success');
} else {
- toast.error(errorText);
+ notify(errorText, 'error');
}
}
} catch (error: unknown) {
- toast.error(getErrorMessage(error));
+ notify(getErrorMessage(error), 'error');
} finally {
resetAndClose();
setIsPending(false);
diff --git a/web/packages/studio/src/components/DeleteConfirmationModal/index.tsx b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx
similarity index 83%
rename from web/packages/studio/src/components/DeleteConfirmationModal/index.tsx
rename to web/packages/common/src/components/DeleteConfirmationModal/index.tsx
index f4411770b1..b23541f732 100644
--- a/web/packages/studio/src/components/DeleteConfirmationModal/index.tsx
+++ b/web/packages/common/src/components/DeleteConfirmationModal/index.tsx
@@ -1,8 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal';
import { FormModalProps } from '@nemo/common/src/components/FormModal';
-import { ConfirmationModal } from '@studio/components/modals/ConfirmationModal';
+import type { NotifyFn } from '@nemo/common/src/providers/toast/types';
import { FC } from 'react';
interface DeleteModalProps extends Pick {
@@ -14,6 +15,8 @@ interface DeleteModalProps extends Pick {
successText?: string;
errorText?: string;
suppressResultToasts?: boolean;
+ /** Where result messages go. Defaults to the surrounding ToastProvider; plugins pass `host.notifications.notify`. */
+ onNotify?: NotifyFn;
}
export const DeleteConfirmationModal: FC = ({
diff --git a/web/packages/studio/src/components/ErrorPanel/index.tsx b/web/packages/common/src/components/ErrorPanel/index.tsx
similarity index 96%
rename from web/packages/studio/src/components/ErrorPanel/index.tsx
rename to web/packages/common/src/components/ErrorPanel/index.tsx
index 885c0c2950..2691b78ed7 100644
--- a/web/packages/studio/src/components/ErrorPanel/index.tsx
+++ b/web/packages/common/src/components/ErrorPanel/index.tsx
@@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
+import { logger } from '@nemo/common/src/utils/logger';
import { Flex, PageHeader, Panel, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { logger } from '@studio/util/logger';
import { FileX } from 'lucide-react';
import { ComponentProps, FC, ReactNode, useEffect } from 'react';
import { useRouteError, isRouteErrorResponse } from 'react-router';
diff --git a/web/packages/studio/src/components/ExpandableMessage/index.test.tsx b/web/packages/common/src/components/ExpandableMessage/index.test.tsx
similarity index 98%
rename from web/packages/studio/src/components/ExpandableMessage/index.test.tsx
rename to web/packages/common/src/components/ExpandableMessage/index.test.tsx
index 5c0c79bd43..9d71ec0fad 100644
--- a/web/packages/studio/src/components/ExpandableMessage/index.test.tsx
+++ b/web/packages/common/src/components/ExpandableMessage/index.test.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ExpandableMessage } from '@studio/components/ExpandableMessage';
+import { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
diff --git a/web/packages/studio/src/components/ExpandableMessage/index.tsx b/web/packages/common/src/components/ExpandableMessage/index.tsx
similarity index 100%
rename from web/packages/studio/src/components/ExpandableMessage/index.tsx
rename to web/packages/common/src/components/ExpandableMessage/index.tsx
diff --git a/web/packages/studio/src/components/FileTag/index.tsx b/web/packages/common/src/components/FileTag/index.tsx
similarity index 100%
rename from web/packages/studio/src/components/FileTag/index.tsx
rename to web/packages/common/src/components/FileTag/index.tsx
diff --git a/web/packages/studio/src/components/FileUpload/index.tsx b/web/packages/common/src/components/FileUpload/index.tsx
similarity index 95%
rename from web/packages/studio/src/components/FileUpload/index.tsx
rename to web/packages/common/src/components/FileUpload/index.tsx
index 5735e70cc0..ee624b4417 100644
--- a/web/packages/studio/src/components/FileUpload/index.tsx
+++ b/web/packages/common/src/components/FileUpload/index.tsx
@@ -1,14 +1,14 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { Block, Flex, Label, Stack, Text } from '@nvidia/foundations-react-core';
-import { FileTag, FileTagStatus } from '@studio/components/FileTag';
+import { FileTag, FileTagStatus } from '@nemo/common/src/components/FileTag';
import {
getAcceptedFileExtensions,
hadInvalidFileTypeError,
hadTooManyFilesError,
-} from '@studio/components/FileUpload/util';
-import { InputErrorText } from '@studio/components/InputErrorText';
+} from '@nemo/common/src/components/FileUpload/util';
+import { InputErrorText } from '@nemo/common/src/components/InputErrorText';
+import { Block, Flex, Label, Stack, Text } from '@nvidia/foundations-react-core';
import { FilePlus, FileText } from 'lucide-react';
import { FC, MouseEvent, MouseEventHandler, ReactNode, useCallback, useState } from 'react';
import { DropEvent, DropzoneOptions, FileRejection, useDropzone } from 'react-dropzone';
diff --git a/web/packages/studio/src/components/FileUpload/util.ts b/web/packages/common/src/components/FileUpload/util.ts
similarity index 100%
rename from web/packages/studio/src/components/FileUpload/util.ts
rename to web/packages/common/src/components/FileUpload/util.ts
diff --git a/web/packages/studio/src/components/InputErrorText/index.tsx b/web/packages/common/src/components/InputErrorText/index.tsx
similarity index 100%
rename from web/packages/studio/src/components/InputErrorText/index.tsx
rename to web/packages/common/src/components/InputErrorText/index.tsx
diff --git a/web/packages/common/src/components/LogViewer/index.tsx b/web/packages/common/src/components/LogViewer/index.tsx
index f0f87a380d..3fa157d2cf 100644
--- a/web/packages/common/src/components/LogViewer/index.tsx
+++ b/web/packages/common/src/components/LogViewer/index.tsx
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { useStickToBottom } from '@nemo/common/src/hooks/useStickToBottom';
-import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import type { NotifyFn } from '@nemo/common/src/providers/toast/types';
+import { useNotify } from '@nemo/common/src/providers/toast/useNotify';
import { triggerDownload } from '@nemo/common/src/utils/file';
import { formatLogs } from '@nemo/common/src/utils/logs';
import type { PlatformJobLog } from '@nemo/sdk/generated/platform/schema';
@@ -27,6 +28,8 @@ interface LogViewerProps {
downloadFilename?: string;
rows?: number;
emptyMessage?: string;
+ /** Where the copy confirmation goes. Defaults to the surrounding ToastProvider; plugins pass `host.notifications.notify`. */
+ onNotify?: NotifyFn;
}
export const LogViewer: FC = ({
@@ -35,6 +38,7 @@ export const LogViewer: FC = ({
downloadFilename,
rows = DEFAULT_ROW_COUNT,
emptyMessage = 'No logs available yet',
+ onNotify,
}) => {
const [showAllLogs, setShowAllLogs] = useState(false);
const [wrapLines, setWrapLines] = useState(false);
@@ -45,7 +49,7 @@ export const LogViewer: FC = ({
const isShowingLogs = useMemo(() => logs.length > 0 && !isLoading, [logs.length, isLoading]);
- const { success } = useToast();
+ const notify = useNotify(onNotify);
const { ref: codeScrollRef, scrollToBottom } = useStickToBottom({
enabled: isShowingLogs,
@@ -87,7 +91,7 @@ export const LogViewer: FC = ({
kind="block"
collapsible={false}
rows={rows}
- onCopySuccess={() => success('Copied to clipboard!', { durationMs: 3000 })}
+ onCopySuccess={() => notify('Copied to clipboard!', 'success', { durationMs: 3000 })}
className="min-h-auto h-full"
attributes={{
CodeSnippetCode: {
diff --git a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx
similarity index 93%
rename from web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx
rename to web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx
index 9557eaa76c..168e7c1806 100644
--- a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx
+++ b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.test.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
+import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
diff --git a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx b/web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx
similarity index 100%
rename from web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx
rename to web/packages/common/src/components/QuickActionsMenu/QuickActionsMenuRoot/index.tsx
diff --git a/web/packages/common/src/plugin.ts b/web/packages/common/src/plugin.ts
index 8367f0c3bc..c0a2a4c864 100644
--- a/web/packages/common/src/plugin.ts
+++ b/web/packages/common/src/plugin.ts
@@ -4,6 +4,20 @@
// Plugin API: the surface Studio serves to plugin bundles as `@nemo/common`.
// Removals are breaking. Explicit exports, not `export *`.
+export { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
+export { AccordionSection } from '@nemo/common/src/components/AccordionSection';
+export { ConfirmationModal } from '@nemo/common/src/components/ConfirmationModal';
+export { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
+export type { AccordionSectionProps } from '@nemo/common/src/components/AccordionSection';
+export { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage';
+export { FileTag } from '@nemo/common/src/components/FileTag';
+export type { FileTagProps, FileTagStatus } from '@nemo/common/src/components/FileTag';
+export { FileUpload } from '@nemo/common/src/components/FileUpload';
+export type { FileUploadProps, RenderFileTagFn } from '@nemo/common/src/components/FileUpload';
+export { InputErrorText } from '@nemo/common/src/components/InputErrorText';
+export { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
+export type { QuickActionItem } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
+
export {
StudioDataView,
StudioDataViewToolbar,
@@ -46,6 +60,13 @@ export {
} from '@nemo/common/src/utils/query';
export { triggerDownload } from '@nemo/common/src/utils/file';
+export { getErrorMessage } from '@nemo/common/src/utils/error';
+export { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
+export { logger, toError } from '@nemo/common/src/utils/logger';
+
+export type { NotifyFn, NotifyType } from '@nemo/common/src/providers/toast/types';
+
+export { JOB_POLLING_INTERVAL_MS } from '@nemo/common/src/constants';
export {
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
diff --git a/web/packages/common/src/providers/toast/types.ts b/web/packages/common/src/providers/toast/types.ts
index 8143683135..362f679296 100644
--- a/web/packages/common/src/providers/toast/types.ts
+++ b/web/packages/common/src/providers/toast/types.ts
@@ -4,7 +4,7 @@
import { Toast } from '@nvidia/foundations-react-core';
import { ComponentProps, ReactNode } from 'react';
-interface MessageFnOptions {
+export interface MessageFnOptions {
durationMs?: number | false; // How long should the toast appear for? Pass false to prevent auto-dismiss.
}
interface AddToastFnOptions extends MessageFnOptions {
@@ -27,6 +27,15 @@ export interface ToastObject {
dismissToast: DismissToastFn;
}
+export type NotifyType = 'success' | 'error' | 'info' | 'warning';
+
+/**
+ * Provider-independent notification sink. Components exposed across the plugin
+ * boundary take this as a prop so a plugin can route them at Studio's toaster
+ * without sharing a ToastContext module instance.
+ */
+export type NotifyFn = (message: string, type?: NotifyType, options?: MessageFnOptions) => void;
+
export interface ToastContextValue {
addToast: AddToastFn;
dismissToast: DismissToastFn;
diff --git a/web/packages/common/src/providers/toast/useNotify.test.tsx b/web/packages/common/src/providers/toast/useNotify.test.tsx
new file mode 100644
index 0000000000..93b6ce1b71
--- /dev/null
+++ b/web/packages/common/src/providers/toast/useNotify.test.tsx
@@ -0,0 +1,97 @@
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { ToastProvider } from '@nemo/common/src/providers/toast/ToastProvider';
+import type { MessageFnOptions, NotifyFn } from '@nemo/common/src/providers/toast/types';
+import { useNotify } from '@nemo/common/src/providers/toast/useNotify';
+import { logger } from '@nemo/common/src/utils/logger';
+import { act, render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import type { FC } from 'react';
+
+interface NotifierProps {
+ onNotify?: NotifyFn;
+ options?: MessageFnOptions;
+}
+
+const Notifier: FC = ({ onNotify, options }) => {
+ const notify = useNotify(onNotify);
+ return ;
+};
+
+describe('useNotify', () => {
+ const user = userEvent.setup();
+
+ it('sends to the surrounding ToastProvider when no onNotify is given', async () => {
+ render(
+
+
+
+ );
+
+ await user.click(screen.getByRole('button', { name: 'Notify' }));
+
+ expect(await screen.findByText('All done')).toBeInTheDocument();
+ });
+
+ it('prefers onNotify over the provider', async () => {
+ const onNotify = vi.fn();
+ render(
+
+
+
+ );
+
+ await user.click(screen.getByRole('button', { name: 'Notify' }));
+
+ expect(onNotify).toHaveBeenCalledWith('All done', 'success', undefined);
+ expect(screen.queryByText('All done')).not.toBeInTheDocument();
+ });
+
+ it('works without a provider, which is the plugin case', async () => {
+ const onNotify = vi.fn();
+ render();
+
+ await user.click(screen.getByRole('button', { name: 'Notify' }));
+
+ expect(onNotify).toHaveBeenCalledWith('All done', 'success', undefined);
+ });
+
+ it('forwards toast options to onNotify', async () => {
+ const onNotify = vi.fn();
+ render();
+
+ await user.click(screen.getByRole('button', { name: 'Notify' }));
+
+ expect(onNotify).toHaveBeenCalledWith('All done', 'success', { durationMs: false });
+ });
+
+ it('honours durationMs on the provider path', async () => {
+ vi.useFakeTimers({ shouldAdvanceTime: true });
+ render(
+
+
+
+ );
+
+ await user.click(screen.getByRole('button', { name: 'Notify' }));
+ expect(await screen.findByText('All done')).toBeInTheDocument();
+
+ await act(async () => {
+ vi.advanceTimersByTime(10_000);
+ });
+ expect(screen.getByText('All done')).toBeInTheDocument();
+
+ vi.useRealTimers();
+ });
+
+ it('drops the message with a warning when there is no sink at all', async () => {
+ const warn = vi.spyOn(logger, 'warn').mockImplementation(() => {});
+ render();
+
+ await user.click(screen.getByRole('button', { name: 'Notify' }));
+
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('All done'));
+ warn.mockRestore();
+ });
+});
diff --git a/web/packages/common/src/providers/toast/useNotify.ts b/web/packages/common/src/providers/toast/useNotify.ts
new file mode 100644
index 0000000000..af55a9bc0d
--- /dev/null
+++ b/web/packages/common/src/providers/toast/useNotify.ts
@@ -0,0 +1,32 @@
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import type { NotifyFn } from '@nemo/common/src/providers/toast/types';
+import { ToastContext } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
+import { useCallback, useContext } from 'react';
+
+/**
+ * Resolves a notification sink: an explicit `onNotify` first, then the
+ * ToastProvider above this component. Unlike `useToast` this never throws —
+ * a plugin renders against a different `ToastContext` instance than the one
+ * Studio mounts, so the context is legitimately absent there.
+ */
+export const useNotify = (onNotify?: NotifyFn): NotifyFn => {
+ const toast = useContext(ToastContext)?.toast;
+
+ return useCallback(
+ (message, type = 'info', options) => {
+ if (onNotify) {
+ onNotify(message, type, options);
+ return;
+ }
+ if (toast) {
+ toast[type](message, options);
+ return;
+ }
+ logger.warn(`[toast] dropped notification, no ToastProvider and no onNotify: ${message}`);
+ },
+ [onNotify, toast]
+ );
+};
diff --git a/web/packages/common/src/utils/error.ts b/web/packages/common/src/utils/error.ts
new file mode 100644
index 0000000000..f0a843780e
--- /dev/null
+++ b/web/packages/common/src/utils/error.ts
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/** Display message from an unknown error; falls back when it is not an Error. */
+export function getErrorMessage(error: unknown, defaultMessage?: string): string {
+ return error instanceof Error
+ ? error.message
+ : (defaultMessage ?? 'Something went wrong. Please try again.');
+}
diff --git a/web/packages/studio/src/util/forms/error.test.ts b/web/packages/common/src/utils/forms/error.test.ts
similarity index 94%
rename from web/packages/studio/src/util/forms/error.test.ts
rename to web/packages/common/src/utils/forms/error.test.ts
index 128e9cc50c..90cbfd4ee7 100644
--- a/web/packages/studio/src/util/forms/error.test.ts
+++ b/web/packages/common/src/utils/forms/error.test.ts
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
-import { logger } from '@studio/util/logger';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
+import { logger } from '@nemo/common/src/utils/logger';
import { FieldErrors } from 'react-hook-form';
// Mock the websiteLogger
-vi.mock('@studio/util/logger', () => ({
+vi.mock('@nemo/common/src/utils/logger', () => ({
logger: {
error: vi.fn(),
},
diff --git a/web/packages/studio/src/util/forms/error.ts b/web/packages/common/src/utils/forms/error.ts
similarity index 94%
rename from web/packages/studio/src/util/forms/error.ts
rename to web/packages/common/src/utils/forms/error.ts
index fd9502e4e2..49580c2e9e 100644
--- a/web/packages/studio/src/util/forms/error.ts
+++ b/web/packages/common/src/utils/forms/error.ts
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { logger } from '@studio/util/logger';
+import { logger } from '@nemo/common/src/utils/logger';
import { FieldErrors } from 'react-hook-form';
/**
diff --git a/web/packages/studio/src/util/logger.ts b/web/packages/common/src/utils/logger.ts
similarity index 83%
rename from web/packages/studio/src/util/logger.ts
rename to web/packages/common/src/utils/logger.ts
index 519a8f6749..714c7b6d48 100644
--- a/web/packages/studio/src/util/logger.ts
+++ b/web/packages/common/src/utils/logger.ts
@@ -3,9 +3,15 @@
/* eslint-disable no-console */
import { Logger, logs, SeverityNumber } from '@opentelemetry/api-logs';
-import { OTEL_SERVICE_NAME, VERSION_SHA } from '@studio/constants/environment';
-const otelLogger: Logger = logs.getLogger(OTEL_SERVICE_NAME);
+// The service name lives in Studio's env config, which a shared library must not
+// import. Studio calls configureLogger() at bootstrap; until then, logs still go
+// to the console and OTEL receives them under a placeholder name.
+let otelLogger: Logger = logs.getLogger('nemo-studio');
+
+export const configureLogger = (serviceName: string): void => {
+ otelLogger = logs.getLogger(serviceName);
+};
/**
* Wrapper class around the global OpenTelemetry Logger. Logs will be transported to:
@@ -18,8 +24,8 @@ const otelLogger: Logger = logs.getLogger(OTEL_SERVICE_NAME);
* Example usage:
*
* ```
- * import { websiteLogger } from '@studio/util/logger';
- * websiteLogger.info('Some message to log');
+ * import { logger } from '@nemo/common/src/utils/logger';
+ * logger.info('Some message to log');
* ```
*/
class WebsiteLogger {
@@ -82,12 +88,3 @@ export const handleGenericError = (error: Error | string) => {
logger.error(error);
}
};
-
-/**
- * Logs the version of the app to the website logger.
- */
-export const logVersion = async () => {
- if (VERSION_SHA) {
- logger.info(`Version: ${VERSION_SHA}`);
- }
-};
diff --git a/web/packages/studio/src/App.tsx b/web/packages/studio/src/App.tsx
index 0876070c24..b8a87f1a6b 100644
--- a/web/packages/studio/src/App.tsx
+++ b/web/packages/studio/src/App.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { configureLogger } from '@nemo/common/src/utils/logger';
import {
ThemeProvider as KaizenThemeProvider,
Theme,
@@ -13,12 +14,13 @@ import {
AUTH_SCOPE_PREFIX,
AUTH_SCOPES,
BASE_URL,
+ OTEL_SERVICE_NAME,
} from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { routes } from '@studio/routes';
import { useLocalStorage } from '@studio/util/hooks/useLocalStorage';
import { UI_THEME } from '@studio/util/localStorage';
-import { logVersion } from '@studio/util/logger';
+import { logVersion } from '@studio/util/logVersion';
import { QueryClientProvider } from '@tanstack/react-query';
import { WebStorageStateStore } from 'oidc-client-ts';
import { StrictMode } from 'react';
@@ -55,6 +57,7 @@ if (import.meta.hot) {
}
const router = createBrowserRouter(routes, { basename: BASE_URL ?? '/' });
+configureLogger(OTEL_SERVICE_NAME);
logVersion();
const effectiveScope = expandScopes(AUTH_SCOPES || 'openid profile email', AUTH_SCOPE_PREFIX);
diff --git a/web/packages/studio/src/api/datasets/useDatasetFileContent.ts b/web/packages/studio/src/api/datasets/useDatasetFileContent.ts
index 6564437eb8..804c7b4243 100644
--- a/web/packages/studio/src/api/datasets/useDatasetFileContent.ts
+++ b/web/packages/studio/src/api/datasets/useDatasetFileContent.ts
@@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { customFetch } from '@nemo/sdk/generated/fetchers/platform';
import { filesDownloadFile, getFilesDownloadFileQueryKey } from '@nemo/sdk/generated/platform/api';
import type { EntityIdentifier } from '@studio/api/common/types';
import { getDatasetFileContentQueryKey } from '@studio/api/datasets/invalidateDatasetCaches';
import { PLATFORM_BASE_URL } from '@studio/constants/environment';
import { isBinaryExtension } from '@studio/util/binaryFile';
-import { logger } from '@studio/util/logger';
import { queryOptions, useQuery, UseQueryOptions, useSuspenseQuery } from '@tanstack/react-query';
import axios from 'axios';
import { parquetRead } from 'hyparquet';
diff --git a/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts b/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts
index 0e14950c8d..145498f8fb 100644
--- a/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts
+++ b/web/packages/studio/src/api/guardrail-checks/guardrailChecks.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { isVersionConflictError } from '@nemo/common/src/api/common/utils';
import {
entitiesCreateEntity,
entitiesDeleteEntityByName,
@@ -16,7 +17,6 @@ import type {
GuardrailCheckResponse,
RailsConfig,
} from '@nemo/sdk/generated/platform/schema';
-import { isVersionConflictError } from '@studio/api/common/utils';
import {
GUARDRAIL_CHECKS_ENTITY_TYPE,
type GuardrailCheckData,
diff --git a/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx b/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx
index 04b458462f..7c4da6ff9c 100644
--- a/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx
+++ b/web/packages/studio/src/components/AnonymizerJobActionsMenu/index.tsx
@@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import {
+ type QuickActionItem,
+ QuickActionsMenuRoot,
+} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { CJobCancellableStatuses } from '@nemo/common/src/constants/query';
import {
getAnonymizerListRunJobsQueryKey,
@@ -8,10 +12,6 @@ import {
} from '@nemo/sdk/generated/anonymizer/api';
import type { RunJob as AnonymizerJob } from '@nemo/sdk/generated/anonymizer/schema';
import { DeleteJobModal } from '@studio/components/dataViews/AnonymizerJobsDataView/DeleteJobModal';
-import {
- type QuickActionItem,
- QuickActionsMenuRoot,
-} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getAnonymizerJobRoute } from '@studio/routes/utils';
import { useQueryClient } from '@tanstack/react-query';
diff --git a/web/packages/studio/src/components/BulkDeleteModal/index.tsx b/web/packages/studio/src/components/BulkDeleteModal/index.tsx
index 4108f37908..7c1f6f4639 100644
--- a/web/packages/studio/src/components/BulkDeleteModal/index.tsx
+++ b/web/packages/studio/src/components/BulkDeleteModal/index.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { useState } from 'react';
export interface BulkDeleteModalProps {
diff --git a/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts b/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts
index 89e9a3ba48..8c1411450c 100644
--- a/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts
+++ b/web/packages/studio/src/components/CreateFilesetStart/useDescribeWithAi.ts
@@ -4,12 +4,12 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { type ModelWorkspaceGroup } from '@nemo/common/src/api/models/useModels';
import { useChatCompletion } from '@nemo/common/src/hooks/useChatCompletion';
+import { getErrorMessage } from '@nemo/common/src/utils/error';
import type { CreateJobRequest as DataDesignerJobRequest } from '@nemo/sdk/generated/data-designer/schema';
import { buildFixMessages } from '@studio/components/CreateFilesetStart/fixRequest';
import { DATA_DESIGNER_JOB_GENERATOR_SYSTEM_PROMPT } from '@studio/components/NewDataDesignerJobForm/constants';
import { generateDataDesignerJobRequestTool } from '@studio/components/NewDataDesignerJobForm/tools';
import {
- getErrorMessage,
getWorkspaceAndModel,
parseToolResponseToJobRequest,
sanitizeJobRequestName,
diff --git a/web/packages/studio/src/components/CustomizationFileTag/index.tsx b/web/packages/studio/src/components/CustomizationFileTag/index.tsx
index 224e2a4b95..116bdc9e3d 100644
--- a/web/packages/studio/src/components/CustomizationFileTag/index.tsx
+++ b/web/packages/studio/src/components/CustomizationFileTag/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { FileTag, FileTagProps } from '@nemo/common/src/components/FileTag';
import { Stack } from '@nvidia/foundations-react-core';
-import { FileTag, FileTagProps } from '@studio/components/FileTag';
import {
CUSTOMIZATION_FILESET_FILE_ICONS,
CUSTOMIZATION_FILESET_FILE_LABELS,
diff --git a/web/packages/studio/src/components/CustomizationFileUpload/index.tsx b/web/packages/studio/src/components/CustomizationFileUpload/index.tsx
index 3aef53d739..d2daafae1a 100644
--- a/web/packages/studio/src/components/CustomizationFileUpload/index.tsx
+++ b/web/packages/studio/src/components/CustomizationFileUpload/index.tsx
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { RenderFileTagFn } from '@nemo/common/src/components/FileUpload';
import {
CustomizationFileTag,
CustomizationFileTagFile,
} from '@studio/components/CustomizationFileTag';
import { DatasetFileUpload } from '@studio/components/DatasetFileUpload';
-import { RenderFileTagFn } from '@studio/components/FileUpload';
import {
CUSTOMIZATION_FILESET_FILE_PREFIXES,
CustomizationFileType,
diff --git a/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx b/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx
index 24451a5feb..baebe52d63 100644
--- a/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx
+++ b/web/packages/studio/src/components/CustomizationFilesetCreateModal/index.tsx
@@ -6,6 +6,7 @@ import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal
import { FilesetFileUploadError } from '@nemo/common/src/datasets/constants';
import { getURNFromNamedEntityRef } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { FilesetOutput } from '@nemo/sdk/generated/platform/schema';
import { TextInput, TextArea, FormField, Stack } from '@nvidia/foundations-react-core';
import { useDatasetCreate } from '@studio/api/datasets/useDatasetCreate';
@@ -25,7 +26,6 @@ import { datasetSchema } from '@studio/constants/zod';
import { useCustomizationFilesPreview } from '@studio/hooks/useCustomizationFiles';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { renameFile } from '@studio/util/files';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC, useState } from 'react';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
diff --git a/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx b/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx
index 25a502c409..fdf4ebea03 100644
--- a/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx
+++ b/web/packages/studio/src/components/DataDesignerJobActionsMenu/index.tsx
@@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import {
+ type QuickActionItem,
+ QuickActionsMenuRoot,
+} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { CJobCancellableStatuses } from '@nemo/common/src/constants/query';
import {
getDataDesignerListCreateJobsQueryKey,
@@ -9,10 +13,6 @@ import {
import type { CreateJob as DataDesignerJob } from '@nemo/sdk/generated/data-designer/schema';
import { DeleteJobModal } from '@studio/components/dataViews/DataDesignerJobsDataView/DeleteJobModal';
import { buildClonedJobRequest } from '@studio/components/NewDataDesignerJobForm/utils';
-import {
- type QuickActionItem,
- QuickActionsMenuRoot,
-} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getDataDesignerJobBuildRoute, getDataDesignerJobDetailsRoute } from '@studio/routes/utils';
import { useQueryClient } from '@tanstack/react-query';
diff --git a/web/packages/studio/src/components/DatasetCreateModal/index.tsx b/web/packages/studio/src/components/DatasetCreateModal/index.tsx
index 6f0ccab2d9..e270e86465 100644
--- a/web/packages/studio/src/components/DatasetCreateModal/index.tsx
+++ b/web/packages/studio/src/components/DatasetCreateModal/index.tsx
@@ -9,6 +9,7 @@ import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal
import { FilesetFileUploadError } from '@nemo/common/src/datasets/constants';
import { getEntityReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { useFilesUpdateFilesetMetadata } from '@nemo/sdk/generated/platform/api';
import { FilesetOutput } from '@nemo/sdk/generated/platform/schema';
import { Select, Stack } from '@nvidia/foundations-react-core';
@@ -26,7 +27,6 @@ import { CUSTOMIZATION_FILESET_FILE_PREFIXES } from '@studio/constants/customiza
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getFilesetDetailsRoute } from '@studio/routes/utils';
import { renameFile } from '@studio/util/files';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC, useState } from 'react';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
diff --git a/web/packages/studio/src/components/DatasetFileUpload/index.tsx b/web/packages/studio/src/components/DatasetFileUpload/index.tsx
index 78b9f76bc4..e952c50ed6 100644
--- a/web/packages/studio/src/components/DatasetFileUpload/index.tsx
+++ b/web/packages/studio/src/components/DatasetFileUpload/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { FileTag, FileTagStatus } from '@studio/components/FileTag';
-import { RenderFileTagFn, FileUpload } from '@studio/components/FileUpload';
+import { FileTag, FileTagStatus } from '@nemo/common/src/components/FileTag';
+import { RenderFileTagFn, FileUpload } from '@nemo/common/src/components/FileUpload';
import { ComponentProps, FC } from 'react';
export interface DatasetFileUploadProps extends Omit<
diff --git a/web/packages/studio/src/components/DatasetInputFile/index.tsx b/web/packages/studio/src/components/DatasetInputFile/index.tsx
index 8db93d767e..99ca9ada1a 100644
--- a/web/packages/studio/src/components/DatasetInputFile/index.tsx
+++ b/web/packages/studio/src/components/DatasetInputFile/index.tsx
@@ -8,6 +8,7 @@ import { SubmitUploadType } from '@nemo/common/src/components/UploadModal/types'
import { InputFileSchemaType } from '@nemo/common/src/types';
import { extractUserFriendlyKeysFromRow } from '@nemo/common/src/utils/file';
import { validateFileFormat, detectFileStructure } from '@nemo/common/src/utils/fileValidation';
+import { logger } from '@nemo/common/src/utils/logger';
import {
Banner,
Button,
@@ -27,7 +28,6 @@ import type {
import { useDatasetInputFileReducer } from '@studio/components/DatasetInputFile/useDatasetInputFileReducer';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getDatasetDisplayNameFromFilesUrl } from '@studio/util/files';
-import { logger } from '@studio/util/logger';
import { useQueryClient } from '@tanstack/react-query';
import { Plus, CircleCheck, CircleHelp, Eye, File as FileIcon, Trash2 } from 'lucide-react';
import { FC, useCallback, useEffect, useRef } from 'react';
diff --git a/web/packages/studio/src/components/DatasetsTable/index.test.tsx b/web/packages/studio/src/components/DatasetsTable/index.test.tsx
index afc2f0dc4d..17edaede54 100644
--- a/web/packages/studio/src/components/DatasetsTable/index.test.tsx
+++ b/web/packages/studio/src/components/DatasetsTable/index.test.tsx
@@ -52,7 +52,7 @@ vi.mock('@studio/components/BulkDeleteModal', () => ({
),
}));
-vi.mock('@studio/components/DeleteConfirmationModal', () => ({
+vi.mock('@nemo/common/src/components/DeleteConfirmationModal', () => ({
DeleteConfirmationModal: vi.fn(
({
open,
diff --git a/web/packages/studio/src/components/DatasetsTable/index.tsx b/web/packages/studio/src/components/DatasetsTable/index.tsx
index d75df25bd7..c0c84c065d 100644
--- a/web/packages/studio/src/components/DatasetsTable/index.tsx
+++ b/web/packages/studio/src/components/DatasetsTable/index.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
import { getEntityReference } from '@nemo/common/src/namedEntity';
@@ -11,7 +12,6 @@ import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/co
import { makeDatasetsTableColumns } from '@studio/components/DatasetsTable/columns';
import { type DatasetsTableProps } from '@studio/components/DatasetsTable/types';
import { useDatasetsTable } from '@studio/components/DatasetsTable/useDatasetsTable';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { DocumentationButton } from '@studio/components/DocumentationButton';
import { Loading } from '@studio/components/Layouts/Loading';
import { NewDatasetButton } from '@studio/components/NewDatasetButton';
diff --git a/web/packages/studio/src/components/ExperimentCreateModal/index.tsx b/web/packages/studio/src/components/ExperimentCreateModal/index.tsx
index 73237f1bdd..7c72ac1ffe 100644
--- a/web/packages/studio/src/components/ExperimentCreateModal/index.tsx
+++ b/web/packages/studio/src/components/ExperimentCreateModal/index.tsx
@@ -13,6 +13,7 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { FormModal, type FormModalProps } from '@nemo/common/src/components/FormModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { getListExperimentsQueryKey, useCreateExperiment } from '@nemo/sdk/generated/platform/api';
import {
CodeSnippet,
@@ -32,7 +33,6 @@ import {
experimentCreateSchema,
type ExperimentCreateFormFields,
} from '@studio/components/ExperimentCreateModal/constants';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { AxiosError } from 'axios';
import { useState, type FC } from 'react';
import { useForm, type SubmitHandler } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx b/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx
index 82df2da4ac..2501ddcf98 100644
--- a/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx
+++ b/web/packages/studio/src/components/FilesTable/CreateFileSplitsModal/index.tsx
@@ -7,6 +7,7 @@ import { ControlledSelect } from '@nemo/common/src/components/form/ControlledSel
import { FormModal } from '@nemo/common/src/components/FormModal';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { Banner, Divider, Flex, Label, Spinner, Stack, Text } from '@nvidia/foundations-react-core';
import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent';
import { useSplitDatasetFile } from '@studio/api/datasets/useSplitDatasetFile';
@@ -25,7 +26,6 @@ import { ValueWithLabel } from '@studio/components/ValueWithLabel';
import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId';
import { tooltipClassName } from '@studio/styles/common';
import { getContentSchema } from '@studio/util/files';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { getTextWithCount } from '@studio/util/strings';
import { Split } from 'lucide-react';
import { ComponentProps, FC, useMemo } from 'react';
diff --git a/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx b/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx
index e3b272af5e..06631ec5e2 100644
--- a/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx
+++ b/web/packages/studio/src/components/FilesTable/DirectoryQuickActions/index.tsx
@@ -1,16 +1,16 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
+import {
+ QuickActionsMenuRoot,
+ type QuickActionItem,
+} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useQueryParams } from '@nemo/common/src/hooks/useQueryParams';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { useDatasetDirectoryDelete } from '@studio/api/datasets/useDatasetDirectoryDelete';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { FileSystemDirectory } from '@studio/components/FilesTable/utils';
-import {
- QuickActionsMenuRoot,
- type QuickActionItem,
-} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId';
import { QUERY_PARAMETERS } from '@studio/routes/constants';
import { resolveDatasetFilePath } from '@studio/util/files';
diff --git a/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx b/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx
index 1f4b015205..4b92af34f7 100644
--- a/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx
+++ b/web/packages/studio/src/components/FilesTable/FileQuickActions/index.tsx
@@ -1,10 +1,14 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
+import {
+ type QuickActionItem,
+ QuickActionsMenuRoot,
+} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { useDatasetFileDelete } from '@studio/api/datasets/useDatasetFileDelete';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { AddToFolderModal } from '@studio/components/filesets/AddToFolderModal';
import { useBulkDownload } from '@studio/components/filesets/hooks/useBulkDownload';
import { useBulkDuplicate } from '@studio/components/filesets/hooks/useBulkDuplicate';
@@ -12,10 +16,6 @@ import { CreateFileSplitsModal } from '@studio/components/FilesTable/CreateFileS
import { RenameFileModal } from '@studio/components/FilesTable/RenameFileModal';
import { TransformFileModal } from '@studio/components/FilesTable/TransformFileModal';
import { FileSystemFile, FileSystemNode } from '@studio/components/FilesTable/utils';
-import {
- type QuickActionItem,
- QuickActionsMenuRoot,
-} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId';
import { resolveDatasetFilePath } from '@studio/util/files';
import { FC, useState } from 'react';
diff --git a/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx b/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx
index 79b7489891..1c8eb6b4a9 100644
--- a/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx
+++ b/web/packages/studio/src/components/FilesTable/RenameFileModal/index.tsx
@@ -5,9 +5,9 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/Controlled
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { useDatasetFileRename } from '@studio/api/datasets/useDatasetFileRename';
import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx b/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx
index 267cec2d72..161eae907b 100644
--- a/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx
+++ b/web/packages/studio/src/components/FilesTable/TransformFileModal/index.tsx
@@ -7,6 +7,7 @@ import { FormModal } from '@nemo/common/src/components/FormModal';
import { ModelSelect } from '@nemo/common/src/components/ModelSelect';
import { getEntityReference, getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { useModelsListModels } from '@nemo/sdk/generated/platform/api';
import { Divider, Flex, Label, Spinner, Stack, Text } from '@nvidia/foundations-react-core';
import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent';
@@ -20,7 +21,6 @@ import { ValueWithLabel } from '@studio/components/ValueWithLabel';
import { useSelectedDatasetId } from '@studio/hooks/useSelectedDatasetId';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getContentSchema } from '@studio/util/files';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { GitBranch } from 'lucide-react';
import { useMemo, type ComponentProps, type FC } from 'react';
import { useForm } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/FilesetActionMenu/index.tsx b/web/packages/studio/src/components/FilesetActionMenu/index.tsx
index 47b120d96b..ba93ac5b19 100644
--- a/web/packages/studio/src/components/FilesetActionMenu/index.tsx
+++ b/web/packages/studio/src/components/FilesetActionMenu/index.tsx
@@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { getEntityReference } from '@nemo/common/src/namedEntity';
+import { logger } from '@nemo/common/src/utils/logger';
import { useFilesDeleteFileset } from '@nemo/sdk/generated/platform/api';
import type { FilesetOutput } from '@nemo/sdk/generated/platform/schema';
import {
@@ -14,8 +16,6 @@ import {
import { invalidateDatasetCaches } from '@studio/api/datasets/invalidateDatasetCaches';
import { DatasetCreateModal } from '@studio/components/DatasetCreateModal';
import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/constants';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
-import { logger } from '@studio/util/logger';
import { EllipsisVertical } from 'lucide-react';
import { type FC, useState } from 'react';
diff --git a/web/packages/studio/src/components/FilesetCreateModal/index.tsx b/web/packages/studio/src/components/FilesetCreateModal/index.tsx
index 1586a014b9..c0a59a6932 100644
--- a/web/packages/studio/src/components/FilesetCreateModal/index.tsx
+++ b/web/packages/studio/src/components/FilesetCreateModal/index.tsx
@@ -2,18 +2,19 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage as getApiErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal } from '@nemo/common/src/components/FormModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { toValidFilesetName } from '@nemo/common/src/utils/filesetName';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import {
getFilesListFilesetsQueryKey,
useFilesCreateFileset,
} from '@nemo/sdk/generated/platform/api';
import { FilesetOutput } from '@nemo/sdk/generated/platform/schema';
import { SegmentedControl, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage as getApiErrorMessage } from '@studio/api/common/utils';
import {
filesetCreateFormSchema,
type FilesetCreateFormData,
@@ -26,7 +27,6 @@ import { FilesetDetailTab } from '@studio/routes/FilesetDetailRoute/constants';
import { CreateSecretModal } from '@studio/routes/SecretsListRoute/CreateSecretModal';
import { SecretSearchableSelect } from '@studio/routes/SecretsListRoute/SecretSearchableSelect';
import { getFilesetDetailRoute } from '@studio/routes/utils';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import {
isHuggingFaceUrl,
isNgcUrl,
diff --git a/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx b/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx
index 22ac0fa163..a16e6c6dc9 100644
--- a/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx
+++ b/web/packages/studio/src/components/FilesetFilePreviewPanel/components/FileActions/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { triggerDownload } from '@nemo/common/src/utils/file';
import {
@@ -12,7 +13,6 @@ import {
Flex,
} from '@nvidia/foundations-react-core';
import { useDatasetFileDelete } from '@studio/api/datasets/useDatasetFileDelete';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { CreateFileSplitsModal } from '@studio/components/FilesTable/CreateFileSplitsModal';
import { RenameFileModal } from '@studio/components/FilesTable/RenameFileModal';
import { FileSystemFile, FileSystemNode } from '@studio/components/FilesTable/utils';
diff --git a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx
index 6cde2fcbf4..f0300e32e8 100644
--- a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx
+++ b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/AnnotationsPanel.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { formatAbsoluteTimestamp } from '@nemo/common/src/components/RelativeTime/util';
import { DEFAULT_PAGE_SIZE } from '@nemo/common/src/constants/api';
import { useListAnnotations } from '@nemo/sdk/generated/platform/api';
@@ -19,7 +20,6 @@ import {
Text,
TextArea,
} from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { ThumbButton } from '@studio/components/buttons/ThumbButton';
import { useSpanAnnotationActions } from '@studio/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions';
import { MessageSquarePlus, Trash2 } from 'lucide-react';
diff --git a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts
index 8b29b0bad0..f2c56ba0ac 100644
--- a/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts
+++ b/web/packages/studio/src/components/IntakeDetail/IntakeComponents/useSpanAnnotationActions.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import {
getListAnnotationsQueryKey,
listAnnotations,
@@ -13,7 +14,6 @@ import {
type FeedbackAnnotationInputValue,
NoteAnnotationInputKind,
} from '@nemo/sdk/generated/platform/schema';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useQueryClient } from '@tanstack/react-query';
import { useCallback, useState } from 'react';
diff --git a/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx b/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx
index 9b058820b4..a1ed620cff 100644
--- a/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx
+++ b/web/packages/studio/src/components/IntakeDetail/SessionDetailView.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { PageHeader, Stack, StatusMessage } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { TraceDetailLayout } from '@studio/components/IntakeDetail/TraceDetailLayout';
import { TraceSpanTree } from '@studio/components/IntakeDetail/TraceDetailSpanTree';
import { SessionSummaryHeader } from '@studio/components/IntakeDetail/TraceDetailSummaryHeader';
diff --git a/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx b/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx
index d8ad94e2f8..12a8ff5c2d 100644
--- a/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx
+++ b/web/packages/studio/src/components/IntakeDetail/TraceSpanAccordions.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import {
type ListSpansQueryError,
@@ -15,7 +16,6 @@ import {
type Trace,
} from '@nemo/sdk/generated/platform/schema';
import { Flex, Spinner, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { IntakeErrorBanner } from '@studio/components/IntakeDetail/IntakeComponents/IntakeErrorBanner';
import { SpanListView } from '@studio/components/IntakeDetail/TraceSpanListView';
import {
diff --git a/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx b/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx
index de2804f814..baeb838da3 100644
--- a/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx
+++ b/web/packages/studio/src/components/IntakeLists/IntakeSpansTable.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter';
import * as DataView from '@nemo/common/src/components/DataView/internal';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
@@ -16,7 +17,6 @@ import {
type SpanSortField,
} from '@nemo/sdk/generated/platform/schema';
import { Anchor, Button, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { IntakeTelemetryStatusBadge } from '@studio/components/IntakeDetail/IntakeComponents/IntakeTelemetryStatusBadge';
import {
isDefaultStartedAtFilter,
diff --git a/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx b/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx
index 2f89c6bae7..6a47cdf979 100644
--- a/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx
+++ b/web/packages/studio/src/components/IntakeLists/IntakeTracesTable.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { EditColumnsMenu } from '@nemo/common/src/components/DataView/internal';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -9,7 +10,6 @@ import { getSortParamWithWhitelist } from '@nemo/common/src/utils/query';
import { useListTraces } from '@nemo/sdk/generated/platform/api';
import type { Trace, TraceFilter, TraceSortField } from '@nemo/sdk/generated/platform/schema';
import { Button } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import {
isDefaultStartedAtFilter,
makeDefaultStartedAtFilter,
diff --git a/web/packages/studio/src/components/ModelActionsMenu/index.tsx b/web/packages/studio/src/components/ModelActionsMenu/index.tsx
index 720ff45329..5f4c353415 100644
--- a/web/packages/studio/src/components/ModelActionsMenu/index.tsx
+++ b/web/packages/studio/src/components/ModelActionsMenu/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { ModelEntity } from '@nemo/sdk/generated/platform/schema';
-import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { modelToFormData } from '@studio/routes/PromptTuningFormRoute/utils';
import { getPromptTuningFormRoute } from '@studio/routes/utils';
import { GitFork, Trash, FolderOpen } from 'lucide-react';
diff --git a/web/packages/studio/src/components/ModelChat/index.tsx b/web/packages/studio/src/components/ModelChat/index.tsx
index 3865e67ab6..2b37ebba3f 100644
--- a/web/packages/studio/src/components/ModelChat/index.tsx
+++ b/web/packages/studio/src/components/ModelChat/index.tsx
@@ -7,12 +7,12 @@ import {
type AssistantChatProps,
} from '@nemo/common/src/components/AssistantChat';
import type { AssistantMessageCompletion } from '@nemo/common/src/components/AssistantChat/types';
+import { handleGenericError } from '@nemo/common/src/utils/logger';
import type { ModelChatStatus } from '@nemo/common/src/utils/models';
import { DEFAULT_SEED_QUESTIONS } from '@studio/components/chat/defaultSeedQuestions';
import { SeedQuestions } from '@studio/components/chat/SeedQuestions';
import { StatsBadge, type ChatMetrics } from '@studio/components/chat/StatsBadge';
import type { ComposerSeed } from '@studio/routes/ModelCompareRoute/types';
-import { handleGenericError } from '@studio/util/logger';
import { type ReactNode, useEffect, useRef, useState, type FC } from 'react';
interface ModelChatProps extends Pick<
diff --git a/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts b/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts
index d531f9b7dd..251e6438ae 100644
--- a/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts
+++ b/web/packages/studio/src/components/ModelComparePrompts/useModelComparePrompts.ts
@@ -4,6 +4,7 @@
import type { SubmitUploadType } from '@nemo/common/src/components/UploadModal/types';
import { useChatCompletion } from '@nemo/common/src/hooks/useChatCompletion';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
+import { logger } from '@nemo/common/src/utils/logger';
import { type FileSampleMethod } from '@nemo/common/src/utils/sampleTextLines';
import { filesDownloadFile } from '@nemo/sdk/generated/platform/api';
import { SAMPLE_DATASETS } from '@studio/components/chat/sampleDatasets';
@@ -25,7 +26,6 @@ import type {
ResponseResult,
ResponseStats,
} from '@studio/components/ModelComparePrompts/types';
-import { logger } from '@studio/util/logger';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
type UseModelComparePromptsArgs = Pick<
diff --git a/web/packages/studio/src/components/NewCustomizationForm/index.tsx b/web/packages/studio/src/components/NewCustomizationForm/index.tsx
index 6296d06c37..ce7b753b04 100644
--- a/web/packages/studio/src/components/NewCustomizationForm/index.tsx
+++ b/web/packages/studio/src/components/NewCustomizationForm/index.tsx
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { generateDefaultName } from '@nemo/common/src/utils/generateDefaultName';
import {
@@ -17,8 +19,6 @@ import {
Panel,
Stack,
} from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { CustomizationFilesetSelect } from '@studio/components/customizer/CustomizationFilesetSelect';
import { BackendSelectionSection } from '@studio/components/NewCustomizationForm/BackendSelectionSection';
import { ComputeResourcesSection } from '@studio/components/NewCustomizationForm/ComputeResourcesSection';
diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx b/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx
index 4ebac32948..0f562343d4 100644
--- a/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx
+++ b/web/packages/studio/src/components/NewDataDesignerJobForm/JobRequestGenerator.tsx
@@ -10,13 +10,13 @@
import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea';
import { LoadingButton } from '@nemo/common/src/components/LoadingButton';
import { useChatCompletion } from '@nemo/common/src/hooks/useChatCompletion';
+import { getErrorMessage } from '@nemo/common/src/utils/error';
import type { CreateJobRequest as DataDesignerJobRequest } from '@nemo/sdk/generated/data-designer/schema';
import { Flex, Stack, Text } from '@nvidia/foundations-react-core';
import { DATA_DESIGNER_JOB_GENERATOR_SYSTEM_PROMPT } from '@studio/components/NewDataDesignerJobForm/constants';
import { generateDataDesignerJobRequestTool } from '@studio/components/NewDataDesignerJobForm/tools';
import {
applyFormModelToJobRequest,
- getErrorMessage,
getWorkspaceAndModel,
parseJsonContentToJobRequest,
parseToolResponseToJobRequest,
diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts b/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts
index 03d78a2e70..54cd0d2f57 100644
--- a/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts
+++ b/web/packages/studio/src/components/NewDataDesignerJobForm/usePreview.ts
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/utils/error';
import type { DataDesignerConfig } from '@nemo/sdk/generated/data-designer/schema';
import { isAbortError, streamPreview } from '@studio/components/NewDataDesignerJobForm/previewApi';
-import { getErrorMessage } from '@studio/components/NewDataDesignerJobForm/utils';
import { useCallback, useRef, useState } from 'react';
const PREVIEW_PATH_TEMPLATE = '/apis/data-designer/v2/workspaces/:workspace/preview';
diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts
index 28b1c5a00f..6917310cb3 100644
--- a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts
+++ b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.test.ts
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { COPY_NAME_SUFFIX } from '@nemo/common/src/utils/entityName';
+import { getErrorMessage } from '@nemo/common/src/utils/error';
import type {
CreateJob as DataDesignerJob,
CreateJobRequest as DataDesignerJobRequest,
@@ -10,7 +11,6 @@ import {
applyFormModelToJobRequest,
buildClonedJobRequest,
getCloneJobRequestFromState,
- getErrorMessage,
getWorkspaceAndModel,
modelsFromProviders,
PARSE_ERROR_INVALID_JSON,
diff --git a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts
index 64a66aee8b..d1e6208cea 100644
--- a/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts
+++ b/web/packages/studio/src/components/NewDataDesignerJobForm/utils.ts
@@ -12,13 +12,6 @@ import type { ModelEntity, ModelProvider } from '@nemo/sdk/generated/platform/sc
/** Model option for the form: entity plus display name used in job requests */
export type DataDesignerModelOption = ModelEntity & { served_model_name: string };
-/** Get a display message from an unknown error; use default when not an Error instance */
-export function getErrorMessage(error: unknown, defaultMessage?: string): string {
- return error instanceof Error
- ? error.message
- : (defaultMessage ?? 'Something went wrong. Please try again.');
-}
-
/**
* Resolve workspace and model name for chat/completion calls.
* If modelRef contains "/", parses as "workspace/name"; otherwise uses fallbackWorkspace and modelRef as name.
diff --git a/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx b/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx
index cca9c3510e..9d80abbb5f 100644
--- a/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx
+++ b/web/packages/studio/src/components/PromptTuningForm/ImportFromDatasetModal/index.tsx
@@ -3,11 +3,11 @@
import { FormModal } from '@nemo/common/src/components/FormModal';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { Banner, Stack } from '@nvidia/foundations-react-core';
import { useDatasetFileContent } from '@studio/api/datasets/useDatasetFileContent';
import { DatasetFileSelect } from '@studio/components/DatasetFileSelect';
import { DatasetSelect } from '@studio/components/DatasetSelect';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC } from 'react';
import { Controller, FormProvider, useForm, useWatch } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx
index 0d01862983..2f3e9b9ed7 100644
--- a/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx
+++ b/web/packages/studio/src/components/PromptTuningForm/InContextLearningSection/index.tsx
@@ -2,12 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { AccordionSection } from '@nemo/common/src/components/AccordionSection';
import { FileContentPreview } from '@nemo/common/src/components/FileContentPreview';
import { FileList, FileListItem } from '@nemo/common/src/components/FileList';
import { UploadModal } from '@nemo/common/src/components/UploadModal';
import { compileSystemPrompt } from '@nemo/common/src/models/utils';
import { Button, Flex, SidePanel, Stack, Text } from '@nvidia/foundations-react-core';
-import { AccordionSection } from '@studio/components/AccordionSection';
import {
ImportFileContentFormFields,
importFileContentSchema,
diff --git a/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx
index c8e294ee12..72e829e19b 100644
--- a/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx
+++ b/web/packages/studio/src/components/PromptTuningForm/InferenceParametersSection/index.tsx
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccordionSection } from '@nemo/common/src/components/AccordionSection';
import { InferenceParameters } from '@nemo/common/src/components/ModelSelectV2/InferenceParameters';
import type { InferenceParams } from '@nemo/sdk/generated/platform/schema';
-import { AccordionSection } from '@studio/components/AccordionSection';
import type {
PromptTuningFormFields,
PromptTuningFormSectionProps,
diff --git a/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx
index 310c7ac51f..c5cd16d216 100644
--- a/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx
+++ b/web/packages/studio/src/components/PromptTuningForm/PromptTemplateSection/index.tsx
@@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccordionSection } from '@nemo/common/src/components/AccordionSection';
import {
DEFAULT_PROMPT_TEMPLATE,
DEFAULT_PROMPT_TEMPLATE_COMPILED,
} from '@nemo/common/src/models/constants';
import { compileSystemPrompt } from '@nemo/common/src/models/utils';
import { Button, Flex, FormField, Stack, TextArea } from '@nvidia/foundations-react-core';
-import { AccordionSection } from '@studio/components/AccordionSection';
import type {
PromptTuningFormFields,
PromptTuningFormSectionProps,
diff --git a/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx b/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx
index 78b9a5a214..505fa48d82 100644
--- a/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx
+++ b/web/packages/studio/src/components/PromptTuningForm/ToolsSection/index.tsx
@@ -2,9 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { AccordionSection } from '@nemo/common/src/components/AccordionSection';
import { FormModal } from '@nemo/common/src/components/FormModal';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { Anchor, Button, Flex, Stack, Switch, Text } from '@nvidia/foundations-react-core';
-import { AccordionSection } from '@studio/components/AccordionSection';
import { DetailRow } from '@studio/components/DetailRow';
import { AddToolForm } from '@studio/components/PromptTuningForm/ToolsSection/components/AddToolForm';
import { ToolMetadataPanel } from '@studio/components/PromptTuningForm/ToolsSection/components/ToolMetadataPanel';
@@ -15,7 +16,6 @@ import {
import { TOOL_JSON_EXAMPLE } from '@studio/components/PromptTuningForm/ToolsSection/constants';
import { useSubmitSingleTool } from '@studio/components/PromptTuningForm/ToolsSection/hooks/useSubmitSingleTool';
import { PromptTuningFormFields } from '@studio/routes/PromptTuningFormRoute/utils';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { Plus, ExternalLink, Toolbox } from 'lucide-react';
import { FC, useState } from 'react';
import { FormProvider, useForm, useFormContext, useWatch } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx b/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx
index ca8e9a4747..cf44837b12 100644
--- a/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx
+++ b/web/packages/studio/src/components/QuickActionsMenu/QuickActionsMenuDefault/index.tsx
@@ -4,7 +4,7 @@
import {
QuickActionItem,
QuickActionsMenuRoot,
-} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
+} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { Pencil, Trash, FolderOpen } from 'lucide-react';
import { FC, useMemo } from 'react';
import { useNavigate } from 'react-router';
diff --git a/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx b/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx
index 8e14ebe344..b576932038 100644
--- a/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx
+++ b/web/packages/studio/src/components/WorkspaceCreateModal/index.tsx
@@ -13,6 +13,7 @@
import { zodResolver } from '@hookform/resolvers/zod';
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import {
getEntitiesListWorkspacesQueryKey,
useEntitiesCreateWorkspace,
@@ -21,7 +22,6 @@ import { FormField, Stack, TextArea, TextInput } from '@nvidia/foundations-react
import { queryClient } from '@studio/api/queryClient';
import { workspaceCreateSchema } from '@studio/constants/zod';
import { getWorkspaceDetailsDefaultRoute } from '@studio/routes/utils';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { AxiosError } from 'axios';
import { FC } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/WorkspaceDropdown/index.tsx b/web/packages/studio/src/components/WorkspaceDropdown/index.tsx
index 078a66b9eb..b2ede06b9a 100644
--- a/web/packages/studio/src/components/WorkspaceDropdown/index.tsx
+++ b/web/packages/studio/src/components/WorkspaceDropdown/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { useEntitiesListWorkspaces } from '@nemo/sdk/generated/platform/api';
import { Workspace } from '@nemo/sdk/generated/platform/schema';
import {
@@ -23,7 +24,6 @@ import { useRecentWorkspaces } from '@studio/components/WorkspaceDropdown/useRec
import { DEFAULT_LARGE_PAGE_SIZE } from '@studio/constants/constants';
import { getWorkspaceDetailsDefaultRoute } from '@studio/routes/utils';
import { useBoolean } from '@studio/util/hooks/useBoolean';
-import { logger } from '@studio/util/logger';
import cn from 'classnames';
import { Plus, Filter } from 'lucide-react';
import { ChangeEvent, FC, lazy, useMemo, useState } from 'react';
diff --git a/web/packages/studio/src/components/common/SearchBar/index.tsx b/web/packages/studio/src/components/common/SearchBar/index.tsx
index b1bbb0bb74..ab8d6b97b4 100644
--- a/web/packages/studio/src/components/common/SearchBar/index.tsx
+++ b/web/packages/studio/src/components/common/SearchBar/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { FormField, TextInput } from '@nvidia/foundations-react-core';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { Search } from 'lucide-react';
import { ComponentProps } from 'react';
import { useForm, type SubmitHandler } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx b/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx
index c47f5d562a..5b22e58f9c 100644
--- a/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/AgentEvaluationsDataView/index.tsx
@@ -7,6 +7,7 @@ import {
ROW_SELECTION_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -26,7 +27,6 @@ import {
import { Button } from '@nvidia/foundations-react-core';
import { agentNameForJob, evalConfigName } from '@studio/api/evaluation/agent-evaluations';
import { BulkDeleteModal } from '@studio/components/BulkDeleteModal';
-import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { STATUS_FILTER_OPTIONS } from '@studio/constants/platformJobs';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getAgentEvaluationDetailRoute, getFilesetRoute } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx b/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx
index ca2e5a1d85..6520c5f38d 100644
--- a/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/AgentsDataView/index.tsx
@@ -7,6 +7,7 @@ import {
ROW_SELECTION_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -27,7 +28,6 @@ import type { Agent } from '@nemo/sdk/generated/agents/schema/Agent';
import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema/AgentDeployment';
import { Button, Text } from '@nvidia/foundations-react-core';
import { getAgentModelNames } from '@studio/components/dataViews/AgentsDataView/utils';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { DocumentationButton } from '@studio/components/DocumentationButton';
import { MODEL_COMPARE_ENABLED } from '@studio/constants/environment';
import { LINK_DOCS_STUDIO } from '@studio/constants/links';
diff --git a/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx b/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx
index 2c8e50ed55..22147f7b2f 100644
--- a/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/CustomModelsDataView/index.tsx
@@ -8,6 +8,7 @@ import {
ROW_SELECTION_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -33,7 +34,6 @@ import { FINETUNING_TYPE_FILTER_OPTIONS } from '@studio/components/dataViews/Cus
import { CustomizeModelButton } from '@studio/components/dataViews/CustomModelsDataView/CustomizeModelButton';
import { DeploymentIndicator } from '@studio/components/dataViews/CustomModelsDataView/DeploymentIndicator';
import { KindTag } from '@studio/components/dataViews/CustomModelsDataView/KindTag';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { DocumentationButton } from '@studio/components/DocumentationButton';
import { BaseModelSearchFilterField } from '@studio/components/FilterFields';
import type { ModelPanelTab } from '@studio/components/sidePanels/ModelPanels/ModelPanel';
diff --git a/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx b/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx
index b0caedd500..04620f07da 100644
--- a/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/DeploymentsDataView/index.tsx
@@ -10,8 +10,10 @@
* its affiliates is strictly prohibited.
*/
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { withOperators } from '@nemo/common/src/api/filterOperators';
import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -23,8 +25,6 @@ import {
ModelDeploymentStatus,
} from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { CUSTOMIZER_ENABLED } from '@studio/constants/environment';
import { keepPreviousData } from '@tanstack/react-query';
import { Rocket, Trash2 } from 'lucide-react';
diff --git a/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx b/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx
index 003cf1a3ef..6392616b7c 100644
--- a/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/ExperimentDataView/index.tsx
@@ -13,6 +13,7 @@ import {
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
+import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
@@ -33,7 +34,6 @@ import {
} from '@studio/components/dataViews/ExperimentDataView/useExperimentEvaluations';
import { useSortErrorRecovery } from '@studio/components/dataViews/ExperimentDataView/useSortErrorRecovery';
import { deriveEvaluatorNames } from '@studio/components/dataViews/ExperimentDataView/util';
-import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getEvaluationDetailRoute } from '@studio/routes/utils';
import { tooltipClassName } from '@studio/styles/common';
diff --git a/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx b/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx
index 5824138c11..cea8380227 100644
--- a/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/GuardrailsDataView/index.tsx
@@ -1,10 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import {
ROW_ACTIONS_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState';
@@ -14,9 +16,7 @@ import type {
GuardrailsListGuardrailConfigsParams,
} from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { countRails } from '@studio/components/dataViews/GuardrailsDataView/guardrailUtils';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { keepPreviousData } from '@tanstack/react-query';
import { Copy, ShieldCheck, Trash } from 'lucide-react';
import { type ComponentProps, type FC, useCallback } from 'react';
diff --git a/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx b/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx
index 2efbf52139..3bd52a3b05 100644
--- a/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/InferenceProvidersDataView/index.tsx
@@ -10,11 +10,14 @@
* its affiliates is strictly prohibited.
*/
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { withOperators } from '@nemo/common/src/api/filterOperators';
import {
ROW_ACTIONS_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -32,9 +35,6 @@ import {
ModelProviderSort,
} from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Stack, StatusMessage, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { LINK_DOCS_INFERENCE_PROVIDERS } from '@studio/constants/links';
import { EditInferenceProviderModal } from '@studio/routes/InferenceProvidersListRoute/EditInferenceProviderModal';
import { InferenceProviderDetailsSidePanel } from '@studio/routes/InferenceProvidersListRoute/InferenceProviderDetailsSidePanel';
diff --git a/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx b/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx
index 4001b9790e..1ce408cea6 100644
--- a/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/JobsDataView/index.tsx
@@ -1,9 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { withOperators } from '@nemo/common/src/api/filterOperators';
+import { CancelJobButton } from '@nemo/common/src/components/CancelJobButton';
import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter';
import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
@@ -16,15 +19,12 @@ import type {
PlatformJobsListFilter,
} from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, StatusMessage } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { CancelJobButton } from '@studio/components/CancelJobButton';
import {
HIDDEN_JOB_SOURCES,
JOB_SOURCE,
SOURCE_OPTIONS,
} from '@studio/components/dataViews/JobsDataView/constants';
import { getJobDetailRoute } from '@studio/components/dataViews/JobsDataView/utils';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { CUSTOMIZER_ENABLED } from '@studio/constants/environment';
import { LINK_DOCS_JOBS } from '@studio/constants/links';
import { STATUS_FILTER_OPTIONS } from '@studio/constants/platformJobs';
@@ -195,7 +195,12 @@ export const JobsDataView = () => {
enableSorting: false,
cell: ({ row }) => (
-
+
),
}),
diff --git a/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx b/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx
index 2bf792cc50..802e0ae89d 100644
--- a/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/SafeSynthesizerJobsDataView/index.tsx
@@ -9,6 +9,7 @@ import {
ROW_SELECTION_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { ScoreGauge } from '@nemo/common/src/components/ScoreGauge';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
@@ -31,7 +32,6 @@ import { Banner, Button, Stack } from '@nvidia/foundations-react-core';
import { BulkDeleteModal } from '@studio/components/BulkDeleteModal';
import { isCancellableJob } from '@studio/components/dataViews/SafeSynthesizerJobsDataView/utils';
import { DocumentationButton } from '@studio/components/DocumentationButton';
-import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { FilesetFilePreviewLink } from '@studio/components/SafeSynthesizerFilesetPreview/FilesetFilePreviewLink';
import { LINK_DOCS_SAFE_SYNTHESIZER } from '@studio/constants/links';
import { STATUS_FILTER_OPTIONS } from '@studio/constants/platformJobs';
diff --git a/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx b/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx
index e49c4176fd..2709d5e509 100644
--- a/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/SecretsDataView/index.tsx
@@ -10,10 +10,13 @@
* its affiliates is strictly prohibited.
*/
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import {
ROW_ACTIONS_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState';
@@ -21,10 +24,7 @@ import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { useSecretsDeleteSecret, useSecretsListSecrets } from '@nemo/sdk/generated/platform/api';
import { PlatformSecretResponse } from '@nemo/sdk/generated/platform/schema';
import { Button, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { DocumentationButton } from '@studio/components/DocumentationButton';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { LINK_DOCS_SECRETS } from '@studio/constants/links';
import { EditSecretModal } from '@studio/routes/SecretsListRoute/EditSecretModal';
import { keepPreviousData } from '@tanstack/react-query';
diff --git a/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx b/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx
index ae6d1c1941..a4eb248827 100644
--- a/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx
+++ b/web/packages/studio/src/components/dataViews/VirtualModelsDataView/index.tsx
@@ -1,12 +1,15 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { withOperators } from '@nemo/common/src/api/filterOperators';
import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter';
import {
ROW_ACTIONS_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
import { useDeferredUnmount } from '@nemo/common/src/hooks/useDeferredUnmount';
@@ -23,9 +26,6 @@ import type {
VirtualModelFilter,
} from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Stack, StatusMessage, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { BaseModelSearchFilterField } from '@studio/components/FilterFields';
import { VirtualModelDetailsSidePanel } from '@studio/routes/VirtualModelsListRoute/VirtualModelDetailsSidePanel';
import { keepPreviousData, useQueryClient } from '@tanstack/react-query';
diff --git a/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts b/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts
index 3921680d24..b1f8d707d0 100644
--- a/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts
+++ b/web/packages/studio/src/components/evaluation/Configurations/form/InputFile/useInputFile.ts
@@ -9,6 +9,7 @@ import {
FileValidationResult,
FileFormatDetectionResult,
} from '@nemo/common/src/utils/fileValidation';
+import { logger } from '@nemo/common/src/utils/logger';
import { datasetFileContentQueryOptions } from '@studio/api/datasets/useDatasetFileContent';
import { buildTemplatePreview } from '@studio/components/evaluation/Configurations/form/InputFile/helpers';
import {
@@ -18,7 +19,6 @@ import {
} from '@studio/hooks/evaluation/useCreateConfigurationForm';
import { useFileValidation } from '@studio/hooks/evaluation/useFileValidation';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
-import { logger } from '@studio/util/logger';
import { useQueryClient } from '@tanstack/react-query';
import { useState, useCallback, useEffect, useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx b/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx
index b45d8cda96..71656d3dcc 100644
--- a/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx
+++ b/web/packages/studio/src/components/evaluation/Jobs/ActionMenu.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import { useEvaluatorDeleteEvaluateJob } from '@nemo/sdk/generated/evaluator/api';
import type { EvaluateJob } from '@nemo/sdk/generated/evaluator/schema';
import {
@@ -17,7 +18,6 @@ import {
} from '@nvidia/foundations-react-core';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getEvaluationJobName } from '@studio/selectors/evaluationJob';
-import { logger } from '@studio/util/logger';
import { ArrowRight, EllipsisVertical, Trash } from 'lucide-react';
import { FC, useState } from 'react';
diff --git a/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx b/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx
index 07f06747f6..966ceaf292 100644
--- a/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx
+++ b/web/packages/studio/src/components/evaluation/Jobs/DetailsPanel.tsx
@@ -14,6 +14,7 @@ import {
getDifferenceInMilliseconds,
utcToLocalDate,
} from '@nemo/common/src/utils/date';
+import { logger } from '@nemo/common/src/utils/logger';
import {
getEvaluatorGetEvaluateJobQueryKey,
useEvaluatorCancelEvaluateJob,
@@ -23,7 +24,6 @@ import { Banner, Button, Flex, Modal, Panel, Stack, Text } from '@nvidia/foundat
import { ButtonLaunchEvaluation } from '@studio/components/evaluation/ButtonLaunchEvaluation';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getFilesetRoute } from '@studio/routes/utils';
-import { logger } from '@studio/util/logger';
import { useQueryClient } from '@tanstack/react-query';
import { ChartBar, CircleX } from 'lucide-react';
import { useState } from 'react';
diff --git a/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx b/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx
index e10d0f96f2..1942dca878 100644
--- a/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx
+++ b/web/packages/studio/src/components/evaluation/Jobs/EvaluationJobBulkDeleteModal.tsx
@@ -2,11 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import { useEvaluatorDeleteEvaluateJob } from '@nemo/sdk/generated/evaluator/api';
import { Button, Flex, Modal } from '@nvidia/foundations-react-core';
import { useMutateMany } from '@studio/api/common/useMutateMany';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
-import { logger } from '@studio/util/logger';
import { Trash } from 'lucide-react';
import { FC, useState } from 'react';
diff --git a/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx b/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx
index 00465def6e..8236f4754d 100644
--- a/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx
+++ b/web/packages/studio/src/components/evaluation/Jobs/form/ScoreDefinitions.tsx
@@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { InputErrorText } from '@nemo/common/src/components/InputErrorText';
import { Badge, Button, Flex, Stack, Text } from '@nvidia/foundations-react-core';
import {
ScoreModal,
type MetricPanelFormData,
type PanelScoreFormData,
} from '@studio/components/evaluation/Jobs/form/ScoreModal';
-import { InputErrorText } from '@studio/components/InputErrorText';
import { Pencil, Plus, Trash } from 'lucide-react';
import { FC, useState } from 'react';
import { FieldError, useFormContext, useWatch } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx b/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx
index 9e9c366c1c..6b45c6e7f2 100644
--- a/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx
+++ b/web/packages/studio/src/components/filesets/AddToFolderModal/index.tsx
@@ -5,6 +5,7 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { LoadingButton } from '@nemo/common/src/components/LoadingButton';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import { useFilesListFilesetFiles } from '@nemo/sdk/generated/platform/api';
import {
Button,
@@ -28,7 +29,6 @@ import { FileSystemNode } from '@studio/components/FilesTable/utils';
import { useDatasetNavigator } from '@studio/hooks/useDatasetNavigator';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getFilesetDetailsRoute } from '@studio/routes/utils';
-import { logger } from '@studio/util/logger';
import { FolderOpen, Info } from 'lucide-react';
import { type FC, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
diff --git a/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx b/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx
index c1b83bb85e..1bb94bd314 100644
--- a/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx
+++ b/web/packages/studio/src/components/filesets/FilesetFileExplorer/DatasetFileDropzone/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { Block, Flex, Text } from '@nvidia/foundations-react-core';
-import { logger } from '@studio/util/logger';
import { FC, useCallback, ReactNode } from 'react';
import { useDropzone, FileRejection } from 'react-dropzone';
diff --git a/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx b/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx
index 1f14c1a720..94fb303784 100644
--- a/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx
+++ b/web/packages/studio/src/components/filesets/FilesetFileExplorer/NewDirectoryModal/index.tsx
@@ -5,10 +5,10 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { useCreateDirectory } from '@studio/api/datasets/useCreateDirectory';
import { getSiblingFolders } from '@studio/components/filesets/AddToFolderModal/utils';
import { FileSystemNode } from '@studio/components/FilesTable/utils';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
diff --git a/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts b/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts
index 0e555ce47e..242afa577d 100644
--- a/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts
+++ b/web/packages/studio/src/components/filesets/hooks/useBulkDownload.ts
@@ -3,10 +3,10 @@
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { triggerDownload } from '@nemo/common/src/utils/file';
+import { logger } from '@nemo/common/src/utils/logger';
import { useDownloadFileAsArrayBuffer } from '@studio/components/filesets/hooks/useDownloadFileAsArrayBuffer';
import { FileSystemFile } from '@studio/components/FilesTable/utils';
import { getFileNameFromPath } from '@studio/util/files';
-import { logger } from '@studio/util/logger';
import { useCallback, useState } from 'react';
export interface UseBulkDownloadOptions {
diff --git a/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts b/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts
index a97449354f..e4b0176894 100644
--- a/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts
+++ b/web/packages/studio/src/components/filesets/hooks/useBulkDuplicate.ts
@@ -2,11 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import { filesListFilesetFiles } from '@nemo/sdk/generated/platform/api';
import { useDatasetFilesUpload } from '@studio/api/datasets/useDatasetFilesUpload';
import { useDownloadFileAsArrayBuffer } from '@studio/components/filesets/hooks/useDownloadFileAsArrayBuffer';
import { FileSystemFile } from '@studio/components/FilesTable/utils';
-import { logger } from '@studio/util/logger';
import { useCallback, useState } from 'react';
export interface UseBulkDuplicateOptions {
diff --git a/web/packages/studio/src/components/CancelJobButton/index.test.tsx b/web/packages/studio/src/components/promoted/CancelJobButton.test.tsx
similarity index 95%
rename from web/packages/studio/src/components/CancelJobButton/index.test.tsx
rename to web/packages/studio/src/components/promoted/CancelJobButton.test.tsx
index 18c164ce0b..c25666a513 100644
--- a/web/packages/studio/src/components/CancelJobButton/index.test.tsx
+++ b/web/packages/studio/src/components/promoted/CancelJobButton.test.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { CancelJobButton } from '@nemo/common/src/components/CancelJobButton';
import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema';
-import { CancelJobButton } from '@studio/components/CancelJobButton';
import { PLATFORM_BASE_URL } from '@studio/constants/environment';
import { ROUTE_PARAMS } from '@studio/constants/routes';
import { workspace1 } from '@studio/mocks/entity-store/projects';
@@ -18,7 +18,7 @@ const JOB_NAME = 'test-job-abc123';
const renderButton = (status?: PlatformJobStatus) =>
render(
-
+
);
diff --git a/web/packages/studio/src/components/DeleteConfirmationModal/index.test.tsx b/web/packages/studio/src/components/promoted/DeleteConfirmationModal.test.tsx
similarity index 96%
rename from web/packages/studio/src/components/DeleteConfirmationModal/index.test.tsx
rename to web/packages/studio/src/components/promoted/DeleteConfirmationModal.test.tsx
index df9b4f3002..97fba36921 100644
--- a/web/packages/studio/src/components/DeleteConfirmationModal/index.test.tsx
+++ b/web/packages/studio/src/components/promoted/DeleteConfirmationModal.test.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { dataset } from '@studio/mocks/datasets';
import { render, screen } from '@studio/tests/util/render';
import { waitFor } from '@testing-library/react';
diff --git a/web/packages/studio/src/components/ErrorPanel/index.test.tsx b/web/packages/studio/src/components/promoted/ErrorPanel.test.tsx
similarity index 98%
rename from web/packages/studio/src/components/ErrorPanel/index.test.tsx
rename to web/packages/studio/src/components/promoted/ErrorPanel.test.tsx
index 1b32991af6..e86cb4e49c 100644
--- a/web/packages/studio/src/components/ErrorPanel/index.test.tsx
+++ b/web/packages/studio/src/components/promoted/ErrorPanel.test.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { ErrorPanel, ErrorPanelProps } from '@nemo/common/src/components/ErrorPanel';
import { suppressConsoleError } from '@nemo/testing/utils/suppress-console';
-import { ErrorPanel, ErrorPanelProps } from '@studio/components/ErrorPanel';
import { mockUseNavigate } from '@studio/tests/util/mockUseParams';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
diff --git a/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx b/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx
index 78c03333bb..0cad82a3b0 100644
--- a/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx
+++ b/web/packages/studio/src/components/sidePanels/MetricRunSidePanel/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { useModelEntity } from '@nemo/common/src/api/models/useModelEntity';
import { useModelSearch } from '@nemo/common/src/api/models/useModelSearch';
import { VariableButton } from '@nemo/common/src/components/buttons/VariableButton';
@@ -12,6 +13,7 @@ import { ControlledDatasetFileSelect } from '@nemo/common/src/components/Dataset
import type { VariableDef } from '@nemo/common/src/components/form/VariableTextArea';
import { ModelSelectV2 } from '@nemo/common/src/components/ModelSelectV2';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import { useEvaluatorCreateEvaluateJob } from '@nemo/sdk/generated/evaluator/api';
import type { EvaluateJobRequest, MetricInline, Model } from '@nemo/sdk/generated/evaluator/schema';
import {
@@ -23,7 +25,6 @@ import {
Stack,
Text,
} from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import type { MetricItemWithId } from '@studio/components/dataViews/EvaluationMetricsDataView/types';
import { EvalCard } from '@studio/components/evaluation/EvalCard';
import { FileValidationPanel } from '@studio/components/sidePanels/MetricRunSidePanel/FileValidationPanel';
@@ -43,7 +44,6 @@ import { DEFAULT_INFERENCE_PARAMS_FORM_VALUES } from '@studio/hooks/evaluation/u
import { QUERY_PARAMETERS } from '@studio/routes/constants';
import { getEvaluationResultDetailsRoute } from '@studio/routes/utils';
import { buildModelPayload } from '@studio/util/evaluations';
-import { logger } from '@studio/util/logger';
import { Plus } from 'lucide-react';
import { type FC, useCallback, useEffect, useMemo, useState } from 'react';
import {
diff --git a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx
index 5d5ab9db5d..b5ff007294 100644
--- a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx
+++ b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/components.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage';
import { KVPair } from '@nemo/common/src/components/KVPair';
import { parametersToString } from '@nemo/common/src/components/ModelDetailsTooltip/utils';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
@@ -29,7 +30,6 @@ import {
Tooltip,
} from '@nvidia/foundations-react-core';
import { CopyButton } from '@studio/components/CopyButton';
-import { ExpandableMessage } from '@studio/components/ExpandableMessage';
import { getWorkspaceCustomizationJobDetailsRoute } from '@studio/routes/utils';
import { formatFinetuningType } from '@studio/util/customizations';
import { MessagesSquare, Eye, EyeOff, Layers, AlignLeft, AppWindow } from 'lucide-react';
diff --git a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx
index 50328cb4fb..93f01ad042 100644
--- a/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx
+++ b/web/packages/studio/src/components/sidePanels/ModelPanels/ModelPanel/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import {
getModelsListModelsQueryKey,
getProviderProxyGetQueryKey,
@@ -19,7 +20,6 @@ import {
SidePanel,
Stack,
} from '@nvidia/foundations-react-core';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { Empty } from '@studio/components/Empty';
import { Loading } from '@studio/components/Layouts/Loading';
import { ModelChat } from '@studio/components/ModelChat';
diff --git a/web/packages/studio/src/main.tsx b/web/packages/studio/src/main.tsx
index a5cb3a91b2..fa4a7672f9 100644
--- a/web/packages/studio/src/main.tsx
+++ b/web/packages/studio/src/main.tsx
@@ -3,9 +3,9 @@
import '@studio/index.css';
+import { logger } from '@nemo/common/src/utils/logger';
import { App } from '@studio/App';
import { UI_THEME } from '@studio/util/localStorage';
-import { logger } from '@studio/util/logger';
import ReactDOM from 'react-dom/client';
// OpenTelemetry patches fetch/XHR globally, so this must settle before React
diff --git a/web/packages/studio/src/plugins/PluginErrorBoundary.tsx b/web/packages/studio/src/plugins/PluginErrorBoundary.tsx
index 6f078f06c6..18b60752d5 100644
--- a/web/packages/studio/src/plugins/PluginErrorBoundary.tsx
+++ b/web/packages/studio/src/plugins/PluginErrorBoundary.tsx
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
+import { logger } from '@nemo/common/src/utils/logger';
import { Button, PageHeader, Panel, Stack } from '@nvidia/foundations-react-core';
-import { logger } from '@studio/util/logger';
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface PluginErrorBoundaryProps {
diff --git a/web/packages/studio/src/plugins/PluginRenderer.test.tsx b/web/packages/studio/src/plugins/PluginRenderer.test.tsx
index 8b41a2b492..77d3b19a22 100644
--- a/web/packages/studio/src/plugins/PluginRenderer.test.tsx
+++ b/web/packages/studio/src/plugins/PluginRenderer.test.tsx
@@ -4,7 +4,12 @@
import { usePlugins, usePluginsLoaded } from '@studio/plugins/PluginContext';
import { PluginRenderer } from '@studio/plugins/PluginRenderer';
import type { LoadedPlugin, PluginRootProps } from '@studio/plugins/types';
-import { render, screen } from '@testing-library/react';
+import { BreadcrumbsProvider } from '@studio/providers/breadcrumbs/BreadcrumbsProvider';
+import {
+ useBreadcrumbs,
+ type BreadcrumbsItemProps,
+} from '@studio/providers/breadcrumbs/useBreadcrumbs';
+import { act, render, screen } from '@testing-library/react';
import { useEffect } from 'react';
import { MemoryRouter, Route, Routes } from 'react-router';
@@ -52,11 +57,13 @@ vi.mock('@studio/hooks/useWorkspaceFromPath', () => ({
function renderPlugin(pluginName = 'test-plugin') {
return render(
-
-
- } />
-
-
+
+
+
+ } />
+
+
+
);
}
@@ -73,11 +80,16 @@ describe('PluginRenderer', () => {
vi.mocked(usePlugins).mockReturnValue([]);
render(
-
-
- } />
-
-
+
+
+
+ }
+ />
+
+
+
);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
@@ -106,11 +118,16 @@ describe('PluginRenderer', () => {
authState.accessToken = 'renewed-token';
rerender(
-
-
- } />
-
-
+
+
+
+ }
+ />
+
+
+
);
expect(mountSpy).toHaveBeenCalledTimes(1);
@@ -142,4 +159,79 @@ describe('PluginRenderer', () => {
consoleError.mockRestore();
});
+
+ it('writes a plugin breadcrumb trail into Studio and clears it on unmount', () => {
+ const seen: BreadcrumbsItemProps[][] = [];
+ function BreadcrumbSpy() {
+ seen.push(useBreadcrumbs().breadcrumbs);
+ return null;
+ }
+ vi.mocked(usePlugins).mockReturnValue([makePlugin('test-plugin')]);
+
+ // The provider and spy outlive the plugin subtree, so the cleared trail is
+ // still observable after the plugin itself unmounts.
+ const tree = (mounted: boolean) => (
+
+
+ {mounted ? (
+
+
+ }
+ />
+
+
+ ) : null}
+
+ );
+ const { rerender } = render(tree(true));
+
+ act(() =>
+ capturedProps?.host.breadcrumbs.set([
+ { label: 'Runs', href: '/workspaces/ws1/plugin/test-plugin/runs' },
+ { label: 'Run 7' },
+ ])
+ );
+ expect(seen.at(-1)).toEqual([
+ { slotLabel: 'Runs', href: '/workspaces/ws1/plugin/test-plugin/runs' },
+ { slotLabel: 'Run 7', href: undefined },
+ ]);
+
+ rerender(tree(false));
+ // Studio clears the trail itself; a plugin that forgets cannot leave one behind.
+ expect(seen.at(-1)).toEqual([]);
+ });
+
+ it('clears the trail when the router swaps one plugin for another', () => {
+ const seen: BreadcrumbsItemProps[][] = [];
+ function BreadcrumbSpy() {
+ seen.push(useBreadcrumbs().breadcrumbs);
+ return null;
+ }
+ vi.mocked(usePlugins).mockReturnValue([makePlugin('plugin-a'), makePlugin('plugin-b')]);
+
+ render(
+
+
+
+
+ }
+ />
+
+
+
+ );
+
+ act(() => capturedProps?.host.breadcrumbs.set([{ label: 'From plugin A' }]));
+ expect(seen.at(-1)).toEqual([{ slotLabel: 'From plugin A', href: undefined }]);
+
+ // Navigate for real: MemoryRouter only reads initialEntries once, and the
+ // point is that PluginRenderer stays mounted while :pluginName changes, so
+ // only the effect's deps can force the reset.
+ act(() => capturedProps?.host.navigation.navigate('/workspaces/ws1/plugin/plugin-b/'));
+ expect(seen.at(-1)).toEqual([]);
+ });
});
diff --git a/web/packages/studio/src/plugins/PluginRenderer.tsx b/web/packages/studio/src/plugins/PluginRenderer.tsx
index 8d23e9084c..10f888b511 100644
--- a/web/packages/studio/src/plugins/PluginRenderer.tsx
+++ b/web/packages/studio/src/plugins/PluginRenderer.tsx
@@ -2,13 +2,19 @@
// SPDX-License-Identifier: Apache-2.0
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import * as platformSdk from '@nemo/sdk/generated/platform/api';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { usePlugins, usePluginsLoaded } from '@studio/plugins/PluginContext';
import { PluginErrorBoundary } from '@studio/plugins/PluginErrorBoundary';
-import type { PluginHost, PluginSdk, PluginTelemetry } from '@studio/plugins/types';
-import { logger } from '@studio/util/logger';
-import { useCallback, useMemo, useRef, type ReactElement } from 'react';
+import type {
+ PluginBreadcrumb,
+ PluginHost,
+ PluginSdk,
+ PluginTelemetry,
+} from '@studio/plugins/types';
+import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
+import { useCallback, useEffect, useMemo, useRef, type ReactElement } from 'react';
import { useAuth } from 'react-oidc-context';
import { useNavigate, useParams } from 'react-router';
@@ -41,16 +47,28 @@ export const PluginRenderer = (): ReactElement => {
accessTokenRef.current = accessToken;
const getAccessToken = useCallback(() => accessTokenRef.current, []);
+ const { setBreadcrumbs } = useBreadcrumbs();
+ const setPluginBreadcrumbs = useCallback(
+ (trail: PluginBreadcrumb[]) =>
+ setBreadcrumbs(trail.map(({ label, href }) => ({ slotLabel: label, href }))),
+ [setBreadcrumbs]
+ );
+ // Studio owns the cleanup so a plugin can't leave a stale trail behind. Keyed
+ // on pluginName too: the router reuses this component across plugins, and the
+ // outgoing plugin's trail would otherwise persist until the next one sets its own.
+ useEffect(() => () => setBreadcrumbs([]), [setBreadcrumbs, pluginName]);
+
const host = useMemo(
() => ({
workspaceId: workspace,
auth: { accessToken, getAccessToken },
sdk: STUDIO_SDK,
navigation: { navigate: (to) => navigate(to), back: () => navigate(-1) },
- notifications: { notify: (message, type = 'info') => toast[type](message) },
+ notifications: { notify: (message, type = 'info', options) => toast[type](message, options) },
telemetry: makeTelemetry(pluginName ?? 'unknown'),
+ breadcrumbs: { set: setPluginBreadcrumbs },
}),
- [workspace, accessToken, getAccessToken, navigate, toast, pluginName]
+ [workspace, accessToken, getAccessToken, navigate, toast, pluginName, setPluginBreadcrumbs]
);
if (!isLoaded) {
diff --git a/web/packages/studio/src/plugins/types.ts b/web/packages/studio/src/plugins/types.ts
index 9cc4014b26..c3e6f07cf9 100644
--- a/web/packages/studio/src/plugins/types.ts
+++ b/web/packages/studio/src/plugins/types.ts
@@ -20,9 +20,29 @@ export interface PluginNavigation {
export type NotificationType = 'success' | 'error' | 'info' | 'warning';
+export interface NotificationOptions {
+ /** ms before auto-dismiss; `false` keeps the toast until dismissed. Defaults per type. */
+ durationMs?: number | false;
+}
+
/** Fire a toast into Studio's shared toaster; defaults to `info`. */
export interface PluginNotifications {
- notify: (message: string, type?: NotificationType) => void;
+ notify: (message: string, type?: NotificationType, options?: NotificationOptions) => void;
+}
+
+export interface PluginBreadcrumb {
+ label: string;
+ /** Absolute Studio path; omit for the trailing (current) crumb. */
+ href?: string;
+}
+
+/**
+ * Write into Studio's breadcrumb bar, which renders in GlobalNav — outside the
+ * plugin's own subtree, so a plugin cannot render it itself. Studio clears the
+ * trail when the plugin unmounts.
+ */
+export interface PluginBreadcrumbs {
+ set: (trail: PluginBreadcrumb[]) => void;
}
/** Structured logging to Studio's OTEL pipeline, auto-scoped to the plugin. */
@@ -45,6 +65,7 @@ export interface PluginHost {
navigation: PluginNavigation;
notifications: PluginNotifications;
telemetry: PluginTelemetry;
+ breadcrumbs: PluginBreadcrumbs;
}
export interface PluginRootProps {
diff --git a/web/packages/studio/src/plugins/utils.ts b/web/packages/studio/src/plugins/utils.ts
index f6c08fcf2c..0d5c1463bd 100644
--- a/web/packages/studio/src/plugins/utils.ts
+++ b/web/packages/studio/src/plugins/utils.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { PLATFORM_BASE_URL } from '@studio/constants/environment';
import { PLUGINS_MANIFEST_ENDPOINT } from '@studio/plugins/consts';
import { isTrustedBundleUrl } from '@studio/plugins/security';
@@ -10,7 +11,6 @@ import type {
PluginModule,
PluginQueryData,
} from '@studio/plugins/types';
-import { logger } from '@studio/util/logger';
export function isValidPluginManifest(obj: unknown): obj is PluginManifest {
if (typeof obj !== 'object' || obj === null) return false;
diff --git a/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx b/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx
index f670759ff2..ad611a9999 100644
--- a/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx
+++ b/web/packages/studio/src/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import {
useAnonymizerCreateRunJob,
useAnonymizerListEntityLabels,
@@ -15,7 +16,6 @@ import {
SegmentedControl,
Stack,
} from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { parseAnonymizerApiError } from '@studio/routes/AnonymizerBuilderRoute/apiErrors';
import { ColumnsSection } from '@studio/routes/AnonymizerBuilderRoute/components/ColumnsSection';
diff --git a/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx b/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx
index dca9e3f27a..6e344912aa 100644
--- a/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx
+++ b/web/packages/studio/src/routes/AnonymizerBuilderRoute/index.tsx
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { ANONYMIZER_ENABLED } from '@studio/constants/environment';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { AnonymizerBuilderForm } from '@studio/routes/AnonymizerBuilderRoute/components/AnonymizerBuilderForm';
diff --git a/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx b/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx
index 3038d416c9..6a0bc9a874 100644
--- a/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/AnonymizerJobDetailRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { LogViewer } from '@nemo/common/src/components/LogViewer';
import {
useAnonymizerGetRunJob,
@@ -8,7 +9,6 @@ import {
useAnonymizerListRunJobResults,
} from '@nemo/sdk/generated/anonymizer/api';
import { Banner, Grid, PageHeader, Panel, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { AnonymizerJobActionsMenu } from '@studio/components/AnonymizerJobActionsMenu';
import { ANONYMIZER_ENABLED } from '@studio/constants/environment';
import { ROUTE_PARAMS } from '@studio/constants/routes';
diff --git a/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx b/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx
index 860a45daa7..af25f7da34 100644
--- a/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx
+++ b/web/packages/studio/src/routes/AnonymizerListRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { AnonymizerJobsDataView } from '@studio/components/dataViews/AnonymizerJobsDataView';
import { ANONYMIZER_ENABLED } from '@studio/constants/environment';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx b/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx
index 3862c26d82..5356be4001 100644
--- a/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx
+++ b/web/packages/studio/src/routes/AuthSuccessRoute/index.tsx
@@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import { entitiesCreateWorkspace, entitiesGetWorkspace } from '@nemo/sdk/generated/platform/api';
import { Loading } from '@studio/components/Layouts/Loading';
import { useAuthProfile } from '@studio/providers/auth';
-import { logger } from '@studio/util/logger';
import { isAxiosError } from 'axios';
import { FC, useCallback, useEffect } from 'react';
import { useNavigate } from 'react-router';
diff --git a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx
index 3674ecea8c..5ad8f3a80b 100644
--- a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx
+++ b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/DetailActions.tsx
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
+import { QuickActionsMenuRoot } from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
import { CJobCancellableStatuses, CJobLaunchableStatuses } from '@nemo/common/src/constants/query';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import {
@@ -10,8 +12,6 @@ import {
import { getJobsGetJobQueryKey } from '@nemo/sdk/generated/platform/api';
import { PlatformJobStatus, type PlatformJobResponse } from '@nemo/sdk/generated/platform/schema';
import { Button, Flex } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { QuickActionsMenuRoot } from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getNewCustomizationJobRoute, getNewEvaluationMetricRoute } from '@studio/routes/utils';
import { CustomizationBackend, type CustomizationJob } from '@studio/util/customizationBackend';
diff --git a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx
index d32b684e2a..dd1ef7be58 100644
--- a/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx
+++ b/web/packages/studio/src/routes/CustomizationJobDetailsRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { useModelsGetModel } from '@nemo/sdk/generated/platform/api';
import {
PageHeader,
@@ -10,7 +11,6 @@ import {
TabsRoot,
TabsTrigger,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { CustomizationDetailsPanel } from '@studio/components/CustomizationDetailsPanel';
import { CustomizationFilesetDetailsPanel } from '@studio/components/CustomizationFilesetDetailsPanel';
import { Loading } from '@studio/components/Layouts/Loading';
diff --git a/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx b/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx
index 8cf4229040..07bf126beb 100644
--- a/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx
+++ b/web/packages/studio/src/routes/CustomizationJobListRoute/index.tsx
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import type { Adapter, ModelEntity } from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { CustomModelsDataView } from '@studio/components/dataViews/CustomModelsDataView';
import { CustomizeModelButton } from '@studio/components/dataViews/CustomModelsDataView/CustomizeModelButton';
import { ModelPanel, ModelPanelTab } from '@studio/components/sidePanels/ModelPanels/ModelPanel';
diff --git a/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx b/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx
index 3496e00055..9418c9c2fa 100644
--- a/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx
+++ b/web/packages/studio/src/routes/DashboardLandingRoute/index.tsx
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { GradientBackground } from '@nemo/common/src/components/GradientBackground';
import { Button, Flex, Text, TextArea, Tooltip } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { writeStoredActiveSessionId } from '@studio/routes/agents/CopilotChatRoute/activeSessionStorage';
diff --git a/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx b/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx
index 577ec8bc27..f779382f91 100644
--- a/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx
+++ b/web/packages/studio/src/routes/DataDesignerJobBuildRoute/index.tsx
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { DEFAULT_LARGE_PAGE_SIZE } from '@nemo/common/src/constants/api';
import { useDataDesignerCreateJob } from '@nemo/sdk/generated/data-designer/api';
import { useModelsListProviders } from '@nemo/sdk/generated/platform/api';
import { Flex, Stack } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { findTemplate } from '@studio/components/CreateFilesetStart/templates';
import { usePreview } from '@studio/components/NewDataDesignerJobForm/usePreview';
import { getCloneJobRequestFromState } from '@studio/components/NewDataDesignerJobForm/utils';
diff --git a/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx b/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx
index 0a87aa5df0..1358c6a34a 100644
--- a/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx
+++ b/web/packages/studio/src/routes/DataDesignerJobDetailsRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
import { PlatformJobTerminalStatuses } from '@nemo/common/src/constants/query';
@@ -15,7 +16,6 @@ import {
TabsTrigger,
Text,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { DataDesignerJobActionsMenu } from '@studio/components/DataDesignerJobActionsMenu';
import { CreateFileSplitsModal } from '@studio/components/FilesTable/CreateFileSplitsModal';
import { Loading } from '@studio/components/Layouts/Loading';
diff --git a/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx b/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx
index a89a363ea3..c88e8fd5f9 100644
--- a/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx
+++ b/web/packages/studio/src/routes/DataDesignerJobListRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { DataDesignerJobsDataView } from '@studio/components/dataViews/DataDesignerJobsDataView';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts b/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts
index c79c944394..43c197e8a6 100644
--- a/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts
+++ b/web/packages/studio/src/routes/DeploymentsListRoute/CreateDeploymentSidePanel/useCreateDeploymentBySource.ts
@@ -10,6 +10,7 @@
* its affiliates is strictly prohibited.
*/
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import {
@@ -27,7 +28,6 @@ import {
type CreateFilesetRequest,
type CreateModelDeploymentConfigRequest,
} from '@nemo/sdk/generated/platform/schema';
-import { getErrorMessage } from '@studio/api/common/utils';
import {
additionalEnvsFormToApi,
configNameFromWizardBaseName,
diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx b/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx
index 52b10fd881..bf2247d850 100644
--- a/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx
+++ b/web/packages/studio/src/routes/DeploymentsListRoute/index.tsx
@@ -10,12 +10,12 @@
* its affiliates is strictly prohibited.
*/
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { resourceRefSchema, type ResourceRef } from '@nemo/common/src/types';
import { ModelDeployment } from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { DeploymentsDataView } from '@studio/components/dataViews/DeploymentsDataView';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { DocumentationButton } from '@studio/components/DocumentationButton';
import { LINK_DOCS_DEPLOYMENTS } from '@studio/constants/links';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts b/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts
index dca77329a2..7f800913bd 100644
--- a/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts
+++ b/web/packages/studio/src/routes/DeploymentsListRoute/useDeleteDeploymentAndConfig.ts
@@ -12,6 +12,7 @@
import { getPartsFromReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { logger } from '@nemo/common/src/utils/logger';
import {
filesDeleteFileset,
getFilesListFilesetsQueryKey,
@@ -37,7 +38,6 @@ import {
HUGGING_FACE_DEPLOYMENT_SOURCE_VALUE,
huggingFaceSourceFilesetName,
} from '@studio/routes/DeploymentsListRoute/huggingFaceDeploymentArtifacts';
-import { logger } from '@studio/util/logger';
import { type QueryClient, useMutation, useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
diff --git a/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx b/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx
index db6535d0fe..75c0c90d61 100644
--- a/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/EvaluationDetailRoute/index.tsx
@@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { useGetEvaluation, useGetExperiment } from '@nemo/sdk/generated/platform/api';
import { Badge, Card, Flex, PageHeader, Stack, Text } from '@nvidia/foundations-react-core';
import { useOptimizerGetInsight } from '@studio/api/optimizer';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { EvaluationSessionsDataView } from '@studio/components/dataViews/EvaluationSessionsDataView';
import { OriginatingInsightLink } from '@studio/components/OriginatingInsightLink';
import { OPTIMIZER_ENABLED } from '@studio/constants/environment';
diff --git a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx
index 43abdce5eb..eb70792310 100644
--- a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx
+++ b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/SessionCompareColumn.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { KVPair } from '@nemo/common/src/components/KVPair';
import { useListAnnotations } from '@nemo/sdk/generated/platform/api';
@@ -10,7 +11,6 @@ import {
type FeedbackAnnotationInputValue,
} from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Spinner, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { SpanListView } from '@studio/components/IntakeDetail/TraceSpanListView';
import { type NoteRequest } from '@studio/components/IntakeDetail/traceSpanShared';
import { useSessionTrajectories } from '@studio/components/IntakeDetail/useSessionTrajectories';
diff --git a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx
index 9d5effdf91..3d119707ca 100644
--- a/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx
+++ b/web/packages/studio/src/routes/EvaluationSessionDetailRoute/TestCaseCompare.tsx
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import type { EvaluationSessionResponse } from '@nemo/sdk/generated/platform/schema';
import { PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { Loading } from '@studio/components/Layouts/Loading';
import {
type BreadcrumbsItemProps,
diff --git a/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx b/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx
index d4ebddde78..bd097eba36 100644
--- a/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/ExperimentDetailRoute/index.tsx
@@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { useGetExperiment } from '@nemo/sdk/generated/platform/api';
import { Button, Card, Flex, PageHeader, Stack, Text } from '@nvidia/foundations-react-core';
import { useOptimizerGetInsight } from '@studio/api/optimizer';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { ExperimentDataView } from '@studio/components/dataViews/ExperimentDataView';
import { ExperimentEditModal } from '@studio/components/ExperimentEditModal';
import { OriginatingInsightLink } from '@studio/components/OriginatingInsightLink';
diff --git a/web/packages/studio/src/routes/ExperimentRoute/index.tsx b/web/packages/studio/src/routes/ExperimentRoute/index.tsx
index e43c7820b1..b1402b5c9d 100644
--- a/web/packages/studio/src/routes/ExperimentRoute/index.tsx
+++ b/web/packages/studio/src/routes/ExperimentRoute/index.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import type { WithFilterOperators } from '@nemo/common/src/api/filterOperators';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import {
dateTimeFilter,
type DatetimeFilterValue,
@@ -29,7 +30,6 @@ import {
StatusMessage,
Text,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { ExperimentCreateModal } from '@studio/components/ExperimentCreateModal';
import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge';
import { Loading } from '@studio/components/Layouts/Loading';
diff --git a/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx b/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx
index bfa7c41248..dcc9cbc3c4 100644
--- a/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/FilesetDetailRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { creatorToIcon } from '@nemo/common/src/constants/modelMetadata';
import { useQueryParams } from '@nemo/common/src/hooks/useQueryParams';
import { getEntityReference } from '@nemo/common/src/namedEntity';
@@ -19,7 +20,6 @@ import {
TabsTrigger,
Text,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { FilesetActionMenu } from '@studio/components/FilesetActionMenu';
import { Loading } from '@studio/components/Layouts/Loading';
import { ROUTE_PARAMS } from '@studio/constants/routes';
diff --git a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx
index ffeddcb698..85b2e64579 100644
--- a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx
+++ b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.test.tsx
@@ -32,7 +32,7 @@ vi.mock('@studio/components/DatasetCreateModal', () => ({
}),
}));
-vi.mock('@studio/components/DeleteConfirmationModal', () => ({
+vi.mock('@nemo/common/src/components/DeleteConfirmationModal', () => ({
DeleteConfirmationModal: vi.fn(({ open, onClose, onDelete, title }) => {
if (!open) return null;
return (
diff --git a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx
index e2899fc286..292b60468c 100644
--- a/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx
+++ b/web/packages/studio/src/routes/FilesetListRoute/ActionMenu/index.tsx
@@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { getEntityReference } from '@nemo/common/src/namedEntity';
+import { logger } from '@nemo/common/src/utils/logger';
import { useFilesDeleteFileset } from '@nemo/sdk/generated/platform/api';
import { FilesetOutput } from '@nemo/sdk/generated/platform/schema';
import {
@@ -14,8 +16,6 @@ import {
import { invalidateDatasetCaches } from '@studio/api/datasets/invalidateDatasetCaches';
import { DatasetCreateModal } from '@studio/components/DatasetCreateModal';
import { DatasetCreateModalMode } from '@studio/components/DatasetCreateModal/constants';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
-import { logger } from '@studio/util/logger';
import { EllipsisVertical } from 'lucide-react';
import { FC, useState } from 'react';
diff --git a/web/packages/studio/src/routes/FilesetListRoute/index.tsx b/web/packages/studio/src/routes/FilesetListRoute/index.tsx
index f0253f6df5..8b22063f8d 100644
--- a/web/packages/studio/src/routes/FilesetListRoute/index.tsx
+++ b/web/packages/studio/src/routes/FilesetListRoute/index.tsx
@@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { getEntityReference } from '@nemo/common/src/namedEntity';
import { FilesetOutput } from '@nemo/sdk/generated/platform/schema';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { DatasetsTable } from '@studio/components/DatasetsTable';
import { NewDatasetButton } from '@studio/components/NewDatasetButton';
import { NewModelFilesetButton } from '@studio/components/NewModelFilesetButton';
diff --git a/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts b/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts
index 751bccfa58..7a65e3ea6d 100644
--- a/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts
+++ b/web/packages/studio/src/routes/FilesetNewRoute/helpers.ts
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { getErrorMessage as getApiErrorMessage } from '@studio/api/common/utils';
+import { getErrorMessage as getApiErrorMessage } from '@nemo/common/src/api/common/utils';
import { isHuggingFaceUrl, isNgcUrl } from '@studio/util/storageConfigFromUrl';
export function getSampleDatasetName(workspace: string, sampleId: string): string {
diff --git a/web/packages/studio/src/routes/FilesetNewRoute/index.tsx b/web/packages/studio/src/routes/FilesetNewRoute/index.tsx
index 3822a6586d..64737e2d2b 100644
--- a/web/packages/studio/src/routes/FilesetNewRoute/index.tsx
+++ b/web/packages/studio/src/routes/FilesetNewRoute/index.tsx
@@ -6,6 +6,7 @@ import {
checkDatasetQuality,
type DatasetQualityReport,
} from '@nemo/common/src/utils/datasetQuality';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import { FilesetPurpose } from '@nemo/sdk/generated/platform/schema';
import { Button, SegmentedControl, SidePanel, Stack } from '@nvidia/foundations-react-core';
import { useSampleDatasetFiles } from '@studio/api/datasets/useSampleDatasetFiles';
@@ -23,7 +24,6 @@ import {
import { useCreateFileset } from '@studio/routes/FilesetNewRoute/useCreateFileset';
import { CreateSecretModal } from '@studio/routes/SecretsListRoute/CreateSecretModal';
import { getWorkspaceFilesetsRoute } from '@studio/routes/utils';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { isHuggingFaceUrl, isNgcUrl } from '@studio/util/storageConfigFromUrl';
import { QueryObserverResult } from '@tanstack/react-query';
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react';
diff --git a/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts b/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts
index 3f863060b1..fa073fa2b2 100644
--- a/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts
+++ b/web/packages/studio/src/routes/FilesetNewRoute/useCreateFileset.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage as getApiErrorMessage } from '@nemo/common/src/api/common/utils';
import { getEntityReference } from '@nemo/common/src/namedEntity';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import {
@@ -15,7 +16,6 @@ import {
FilesetPurpose,
CreateFilesetRequest,
} from '@nemo/sdk/generated/platform/schema';
-import { getErrorMessage as getApiErrorMessage } from '@studio/api/common/utils';
import { FILESET_DETAILS_ENABLED } from '@studio/constants/environment';
import { DATASET_TYPE_SAMPLE } from '@studio/routes/FilesetNewRoute/constants';
import {
diff --git a/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx b/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx
index 1580eff242..ae8d405a2a 100644
--- a/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx
+++ b/web/packages/studio/src/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/index.tsx
@@ -11,6 +11,7 @@
*/
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { LoadingButton } from '@nemo/common/src/components/LoadingButton';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
@@ -21,7 +22,6 @@ import {
useModelsListProviders,
} from '@nemo/sdk/generated/platform/api';
import { Button, Flex, FormField, SidePanel, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { InferenceModelProviderSelect } from '@studio/routes/InferenceProvidersListRoute/CreateInferenceProviderSidePanel/InferenceModelProviderSelect';
import {
PRESET_CREDENTIALS,
diff --git a/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx b/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx
index 3b9f5bc06d..f047d33e80 100644
--- a/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx
+++ b/web/packages/studio/src/routes/InferenceProvidersListRoute/index.tsx
@@ -10,8 +10,8 @@
* its affiliates is strictly prohibited.
*/
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { InferenceProvidersDataView } from '@studio/components/dataViews/InferenceProvidersDataView';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/routes/IntakeLayout/index.tsx b/web/packages/studio/src/routes/IntakeLayout/index.tsx
index 86c4847820..73a2ba6eaf 100644
--- a/web/packages/studio/src/routes/IntakeLayout/index.tsx
+++ b/web/packages/studio/src/routes/IntakeLayout/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { PageHeader, Stack, Tabs } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge';
import { Loading } from '@studio/components/Layouts/Loading';
import { ROUTES } from '@studio/constants/routes';
diff --git a/web/packages/studio/src/routes/JobDetailRoute/index.tsx b/web/packages/studio/src/routes/JobDetailRoute/index.tsx
index 2e50f2a50a..4461e70768 100644
--- a/web/packages/studio/src/routes/JobDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/JobDetailRoute/index.tsx
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
+import { CancelJobButton } from '@nemo/common/src/components/CancelJobButton';
import { KVPair } from '@nemo/common/src/components/KVPair';
import { LogViewer } from '@nemo/common/src/components/LogViewer';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
@@ -9,8 +11,6 @@ import { useJobLogs } from '@nemo/common/src/hooks/useJobLogs';
import { getJobRefetchInterval } from '@nemo/common/src/utils/query';
import { useJobsGetJob, useJobsListJobResults } from '@nemo/sdk/generated/platform/api';
import { Flex, Grid, PageHeader, Panel, Spinner, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
-import { CancelJobButton } from '@studio/components/CancelJobButton';
import { ROUTE_PARAMS } from '@studio/constants/routes';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
@@ -62,7 +62,7 @@ export const JobDetailRoute: FC = () => {
slotHeading={jobName}
slotActions={
-
+
}
/>
diff --git a/web/packages/studio/src/routes/JobsRoute/index.tsx b/web/packages/studio/src/routes/JobsRoute/index.tsx
index c1f4cd82e4..8dab85a09a 100644
--- a/web/packages/studio/src/routes/JobsRoute/index.tsx
+++ b/web/packages/studio/src/routes/JobsRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { JobsDataView } from '@studio/components/dataViews/JobsDataView';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { FC } from 'react';
diff --git a/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx b/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx
index fa993f6d3b..be6a19acb7 100644
--- a/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx
+++ b/web/packages/studio/src/routes/LegacyNewDataDesignerJobRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { NewDataDesignerJobForm } from '@studio/components/NewDataDesignerJobForm';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx b/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx
index bc9609f8a2..2039c38960 100644
--- a/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx
+++ b/web/packages/studio/src/routes/NewDataDesignerJobRoute/index.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { CreateFilesetStart } from '@studio/components/CreateFilesetStart';
import type { StartSelection } from '@studio/components/CreateFilesetStart/types';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx b/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx
index 827fe61eeb..dd51ede5b1 100644
--- a/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx
+++ b/web/packages/studio/src/routes/PromptTuningFormRoute/index.tsx
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { ChatEmptyState } from '@nemo/common/src/components/Chat/ChatEmptyState';
import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
@@ -25,9 +27,7 @@ import {
Stack,
Text,
} from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { queryClient } from '@studio/api/queryClient';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { Loading } from '@studio/components/Layouts/Loading';
import { ModelChat } from '@studio/components/ModelChat';
import { InContextLearningSection } from '@studio/components/PromptTuningForm/InContextLearningSection';
diff --git a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx
index e3e38b7b1d..5d4d4d2d1b 100644
--- a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx
+++ b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/components/ProgressSection.test.tsx
@@ -8,17 +8,6 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-// Mock the toast hook
-const mockToastSuccess = vi.fn();
-vi.mock('@nemo/common/src/providers/toast/useToast', () => ({
- useToast: () => ({
- success: mockToastSuccess,
- error: vi.fn(),
- info: vi.fn(),
- warning: vi.fn(),
- }),
-}));
-
vi.mock('lucide-react', async () => {
return (await import('@nemo/testing/mocks/lucide')).mockLucideReact(await import('react'));
});
diff --git a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx
index af185d47ae..58c7a922c2 100644
--- a/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx
+++ b/web/packages/studio/src/routes/SafeSynthesizerJobDetailsRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { useJobLogs } from '@nemo/common/src/hooks/useJobLogs';
import { PlatformJobStatus } from '@nemo/sdk/generated/platform/schema';
import {
@@ -8,7 +9,6 @@ import {
useSafeSynthesizerGetJobSuspense as useGetJobV1beta1SafeSynthesizerJobsJobIdGetSuspense,
} from '@nemo/sdk/generated/safe-synthesizer/api';
import { Grid, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { SafeSynthesizerNavigation } from '@studio/components/SafeSynthesizerNavigation';
import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment';
import { ROUTE_PARAMS } from '@studio/constants/routes';
diff --git a/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx
index 11284792f6..a90cb490cd 100644
--- a/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx
+++ b/web/packages/studio/src/routes/SafeSynthesizerJobReportRoute/index.tsx
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import {
useSafeSynthesizerDownloadJobResultSummary as useDownloadJobResultSummaryV1beta1SafeSynthesizerJobsJobIdResultsSummaryDownloadGet,
useSafeSynthesizerGetJobSuspense as useGetJobV1beta1SafeSynthesizerJobsJobIdGetSuspense,
} from '@nemo/sdk/generated/safe-synthesizer/api';
import { Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { SafeSynthesizerNavigation } from '@studio/components/SafeSynthesizerNavigation';
import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment';
import { ROUTE_PARAMS } from '@studio/constants/routes';
diff --git a/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx
index 3e14ffe8dd..b97747d5e5 100644
--- a/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx
+++ b/web/packages/studio/src/routes/SafeSynthesizerListRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { SafeSynthesizerJobsDataView } from '@studio/components/dataViews/SafeSynthesizerJobsDataView';
import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx
index 160cc32092..b41a42d321 100644
--- a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx
+++ b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.test.tsx
@@ -127,7 +127,7 @@ describe('SafeSynthesizerNewRoute', () => {
SAFE_SYNTHESIZER_ENABLED: true,
OTEL_SERVICE_NAME: 'test-service',
}));
- vi.doMock('@studio/util/logger', () => ({
+ vi.doMock('@nemo/common/src/utils/logger', () => ({
logger: { debug: vi.fn(), error: vi.fn(), info: vi.fn(), warn: vi.fn() },
}));
@@ -141,7 +141,7 @@ describe('SafeSynthesizerNewRoute', () => {
SAFE_SYNTHESIZER_ENABLED: false,
OTEL_SERVICE_NAME: 'test-service',
}));
- vi.doMock('@studio/util/logger', () => ({
+ vi.doMock('@nemo/common/src/utils/logger', () => ({
logger: { debug: vi.fn(), error: vi.fn(), info: vi.fn(), warn: vi.fn() },
}));
@@ -795,7 +795,7 @@ describe('SafeSynthesizerNewRoute', () => {
it('should have form validation error handling configured', async () => {
// Remove any lingering vi.doMock for the logger (left by Feature flag tests) so
// the component gets the real logger and we can assert via console.error.
- vi.doUnmock('@studio/util/logger');
+ vi.doUnmock('@nemo/common/src/utils/logger');
vi.doMock('@studio/constants/environment', () => ({
SAFE_SYNTHESIZER_ENABLED: true,
OTEL_SERVICE_NAME: 'test-service',
diff --git a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx
index 6de339e797..3aec570dc4 100644
--- a/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx
+++ b/web/packages/studio/src/routes/SafeSynthesizerNewRoute/index.tsx
@@ -2,9 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
+import { logger } from '@nemo/common/src/utils/logger';
import { useSafeSynthesizerCreateJob } from '@nemo/sdk/generated/safe-synthesizer/api';
import { Banner, Button, Divider, Flex, Panel, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
@@ -18,7 +19,6 @@ import {
getSafeSynthesizerFormDefaults,
} from '@studio/routes/SafeSynthesizerNewRoute/schema';
import { getSafeSynthesizerJobRoute, getSafeSynthesizerRoute } from '@studio/routes/utils';
-import { logger } from '@studio/util/logger';
import { FC, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router';
diff --git a/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx b/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx
index dc3f00254e..f53d4135e2 100644
--- a/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx
+++ b/web/packages/studio/src/routes/SecretsListRoute/CreateSecretModal/index.tsx
@@ -11,6 +11,7 @@
*/
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
@@ -21,7 +22,6 @@ import {
useSecretsCreateSecret,
} from '@nemo/sdk/generated/platform/api';
import { Stack } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useQueryClient } from '@tanstack/react-query';
import { FC } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
diff --git a/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx b/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx
index f173710532..6671c6f2f0 100644
--- a/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx
+++ b/web/packages/studio/src/routes/SecretsListRoute/EditSecretModal/index.tsx
@@ -11,6 +11,7 @@
*/
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledTextArea';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
@@ -21,7 +22,6 @@ import {
} from '@nemo/sdk/generated/platform/api';
import { PlatformSecretResponse } from '@nemo/sdk/generated/platform/schema';
import { FormField, Stack, Text, TextInput } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useQueryClient } from '@tanstack/react-query';
import { FC } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
diff --git a/web/packages/studio/src/routes/SecretsListRoute/index.tsx b/web/packages/studio/src/routes/SecretsListRoute/index.tsx
index bc2fdcbaeb..de9cddc8f0 100644
--- a/web/packages/studio/src/routes/SecretsListRoute/index.tsx
+++ b/web/packages/studio/src/routes/SecretsListRoute/index.tsx
@@ -10,8 +10,8 @@
* its affiliates is strictly prohibited.
*/
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { SecretsDataView } from '@studio/components/dataViews/SecretsDataView';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx b/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx
index ad5c9bcf2b..b2f8c361b5 100644
--- a/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx
+++ b/web/packages/studio/src/routes/VirtualModelsListRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { VirtualModelsDataView } from '@studio/components/dataViews/VirtualModelsDataView';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx
index 89b117628b..9dd5c34533 100644
--- a/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx
+++ b/web/packages/studio/src/routes/WorkspaceBaseModelsRoute/index.tsx
@@ -15,6 +15,7 @@ import {
type ModelEntityFilterInput,
} from '@nemo/common/src/api/entity-store/useBaseModels';
import { usePromptTunableBaseModelIds } from '@nemo/common/src/api/entity-store/usePromptTunableBaseModelIds';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { dateTimeFilter } from '@nemo/common/src/components/DataView/dateTimeFilter';
import * as DataView from '@nemo/common/src/components/DataView/internal';
import { StudioDataView } from '@nemo/common/src/components/DataView/StudioDataView';
@@ -35,7 +36,6 @@ import {
Text,
Tooltip,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { BaseModelCard } from '@studio/components/BaseModelCard';
import { CustomizeModelButton } from '@studio/components/dataViews/CustomModelsDataView/CustomizeModelButton';
import { ModelPanel, ModelPanelTab } from '@studio/components/sidePanels/ModelPanels/ModelPanel';
diff --git a/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx
index be30020efe..b48a500f0b 100644
--- a/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx
+++ b/web/packages/studio/src/routes/WorkspaceDashboardRoute/index.tsx
@@ -1,12 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { GradientBackground } from '@nemo/common/src/components/GradientBackground';
import ModelEvaluationIcon from '@nemo/common/src/svgs/model_evaluation.svg?react';
import ModelPromptTuningIcon from '@nemo/common/src/svgs/model_prompt_tuning.svg?react';
import SafeSynthesizerLogo from '@nemo/common/src/svgs/safe_synthesizer_logo.svg?react';
import { Grid, PageHeader, Stack, Text } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import {
CUSTOMIZER_ENABLED,
EVALUATOR_ENABLED,
diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx
index 7db8d2c1a2..8f77fffe91 100644
--- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx
+++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.test.tsx
@@ -1,9 +1,12 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { PluginContext } from '@studio/plugins/PluginContext';
+import type { LoadedPlugin, PluginNavGroup } from '@studio/plugins/types';
import { WorkspaceSideNav } from '@studio/routes/WorkspaceLayout/WorkspaceSideNav';
import { renderRoute, screen } from '@studio/tests/util/render';
import userEvent from '@testing-library/user-event';
+import type { ReactElement } from 'react';
vi.hoisted(() => {
vi.stubEnv('VITE_FF_OPTIMIZER_ENABLED', 'false');
@@ -24,6 +27,32 @@ const renderSideNav = (history = '/workspaces/test-workspace/dashboard') =>
],
});
+const makePlugin = (name: string, groups: PluginNavGroup[]): LoadedPlugin => ({
+ name,
+ Root: () => null,
+ navItems: () => groups,
+});
+
+const renderWithPlugins = (plugins: LoadedPlugin[]) => {
+ const element: ReactElement = (
+ p.name)),
+ isLoaded: true,
+ isError: false,
+ }}
+ >
+
+
+ );
+
+ return renderRoute(element, {
+ history: '/workspaces/test-workspace/dashboard',
+ routes: [{ path: '/workspaces/:workspace/*', element }],
+ });
+};
+
describe('WorkspaceSideNav', () => {
it('links to the traces view by default', () => {
renderSideNav();
@@ -125,4 +154,100 @@ describe('WorkspaceSideNav', () => {
await user.click(screen.getByRole('link', { name: 'Agents' }));
expect(disclosure('Agents')).toHaveAttribute('aria-expanded', 'true');
});
+
+ it('folds a plugin group into the core group of the same name', () => {
+ renderWithPlugins([
+ makePlugin('red-team', [
+ {
+ group: 'Governance',
+ items: [
+ {
+ id: 'red-team',
+ iconName: 'shield',
+ label: 'Red Team',
+ href: '/workspaces/test-workspace/red-team',
+ },
+ ],
+ },
+ ]),
+ ]);
+
+ expect(screen.getAllByText('Governance')).toHaveLength(1);
+ expect(screen.getByRole('link', { name: 'Red Team' })).toBeInTheDocument();
+ });
+
+ it('appends a plugin group that matches no core group', () => {
+ renderWithPlugins([
+ makePlugin('red-team', [
+ {
+ group: 'Red Team',
+ items: [
+ {
+ id: 'probes',
+ iconName: 'shield',
+ label: 'Probes',
+ href: '/workspaces/test-workspace/probes',
+ },
+ ],
+ },
+ ]),
+ ]);
+
+ expect(screen.getAllByText('Red Team')).toHaveLength(1);
+ expect(screen.getByRole('link', { name: 'Probes' })).toBeInTheDocument();
+ });
+
+ it('keeps a plugin item whose id matches a core item in the merged group', () => {
+ const duplicateKeyWarning = vi.spyOn(console, 'error').mockImplementation(() => {});
+
+ renderWithPlugins([
+ makePlugin('red-team', [
+ {
+ group: 'Governance',
+ items: [
+ {
+ id: 'guardrails',
+ iconName: 'shield',
+ label: 'Red Team Models',
+ href: '/workspaces/test-workspace/red-team-models',
+ },
+ ],
+ },
+ ]),
+ ]);
+
+ expect(screen.getByRole('link', { name: 'Guardrails' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Red Team Models' })).toBeInTheDocument();
+ const keyWarnings = duplicateKeyWarning.mock.calls
+ .map((args) => args.map(String).join(' '))
+ .filter((message) => message.includes('same key'));
+ expect(keyWarnings).toEqual([]);
+
+ duplicateKeyWarning.mockRestore();
+ });
+
+ it('merges groups of the same name across two plugins', () => {
+ renderWithPlugins([
+ makePlugin('one', [
+ {
+ group: 'Governance',
+ items: [
+ { id: 'one', iconName: 'shield', label: 'One', href: '/workspaces/test-workspace/one' },
+ ],
+ },
+ ]),
+ makePlugin('two', [
+ {
+ group: 'Governance',
+ items: [
+ { id: 'two', iconName: 'shield', label: 'Two', href: '/workspaces/test-workspace/two' },
+ ],
+ },
+ ]),
+ ]);
+
+ expect(screen.getAllByText('Governance')).toHaveLength(1);
+ expect(screen.getByRole('link', { name: 'One' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'Two' })).toBeInTheDocument();
+ });
});
diff --git a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx
index 833b977703..dc4469dbf0 100644
--- a/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx
+++ b/web/packages/studio/src/routes/WorkspaceLayout/WorkspaceSideNav.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { NavigationDrawer } from '@studio/components/Layouts/NavigationDrawer';
import type {
NavInputItem,
@@ -56,7 +57,6 @@ import {
getWorkspaceSettingsRoute,
getWorkspaceVirtualModelsRoute,
} from '@studio/routes/utils';
-import { logger } from '@studio/util/logger';
import {
Bot,
Boxes,
@@ -384,7 +384,9 @@ export const WorkspaceSideNav = ({ collapsed }: { collapsed?: boolean }) => {
items: group.items.map((item) => {
const Icon = getPluginIcon(item.iconName);
return {
- id: item.id,
+ // Namespaced: ids are React keys and accordion-state keys, and
+ // merging puts plugin items in the same array as core ones.
+ id: `${plugin.name}:${item.id}`,
slotIcon: Icon ? : undefined,
slotLabel: item.label,
href: item.href,
@@ -399,11 +401,27 @@ export const WorkspaceSideNav = ({ collapsed }: { collapsed?: boolean }) => {
[plugins, workspace]
);
- // A fresh array literal here would re-run NavigationDrawer's own memos on every render.
- const allItems = useMemo(
- () => [...items, ...pluginNavGroups, ...systemNavGroup],
- [items, pluginNavGroups, systemNavGroup]
- );
+ // A plugin naming an existing group joins it rather than rendering a second
+ // header with the same label. Unmatched groups append in plugin order.
+ const allItems = useMemo(() => {
+ const pending = new Map();
+ for (const { group, items: groupItems } of pluginNavGroups) {
+ pending.set(group, [...(pending.get(group) ?? []), ...groupItems]);
+ }
+
+ const merged: NavInputItem[] = items.map((entry) => {
+ if (!isGroup(entry) || entry.group === undefined) return entry;
+ const extra = pending.get(entry.group);
+ if (!extra) return entry;
+ pending.delete(entry.group);
+ return { ...entry, items: [...entry.items, ...extra] };
+ });
+
+ for (const [group, groupItems] of pending) {
+ merged.push({ group, items: groupItems });
+ }
+ return [...merged, ...systemNavGroup];
+ }, [items, pluginNavGroups, systemNavGroup]);
return ;
};
diff --git a/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx b/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx
index 42934b5ab9..2234eba267 100644
--- a/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx
+++ b/web/packages/studio/src/routes/WorkspaceMembersRoute/WorkspaceMemberModal.tsx
@@ -8,6 +8,7 @@ import { ControlledTextInput } from '@nemo/common/src/components/form/Controlled
import { FormModal } from '@nemo/common/src/components/FormModal';
import { RadioCard } from '@nemo/common/src/components/RadioCard';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import {
getEntitiesListWorkspaceMembersQueryKey,
useEntitiesAddWorkspaceMember,
@@ -22,7 +23,6 @@ import {
WORKSPACE_ROLE_DESCRIPTIONS,
type WorkspaceMemberRole,
} from '@studio/routes/WorkspaceMembersRoute/workspaceMemberRoles';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC, useEffect, useMemo } from 'react';
import { Controller, SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
diff --git a/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx
index 3d5ac07f4b..cf40dfe0f2 100644
--- a/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx
+++ b/web/packages/studio/src/routes/WorkspaceMembersRoute/index.tsx
@@ -3,6 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import {
getEntitiesListWorkspaceMembersQueryKey,
useEntitiesListWorkspaceMembers,
@@ -11,9 +13,7 @@ import {
import type { WorkspaceMember } from '@nemo/sdk/generated/platform/schema';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
import { queryClient } from '@studio/api/queryClient';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { MembersDataView } from '@studio/components/dataViews/MembersDataView';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx b/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx
index 3422166e8e..5ded8de630 100644
--- a/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx
+++ b/web/packages/studio/src/routes/WorkspaceSettingsRoute/DeleteWorkspaceModal/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { DEFAULT_WORKSPACE } from '@nemo/common/src/models/constants';
import {
getEntitiesListWorkspacesQueryKey,
@@ -8,7 +9,6 @@ import {
} from '@nemo/sdk/generated/platform/api';
import { WorkspacesPage } from '@nemo/sdk/generated/platform/schema';
import { queryClient } from '@studio/api/queryClient';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { useRecentWorkspaces } from '@studio/components/WorkspaceDropdown/useRecentWorkspaces';
import { getWorkspaceDetailsDefaultRoute } from '@studio/routes/utils';
import { FC } from 'react';
diff --git a/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx b/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx
index ad91728446..8872a18448 100644
--- a/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx
+++ b/web/packages/studio/src/routes/WorkspaceSettingsRoute/EditDescriptionModal/index.tsx
@@ -6,6 +6,7 @@ import { ControlledTextArea } from '@nemo/common/src/components/form/ControlledT
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal, FormModalProps } from '@nemo/common/src/components/FormModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
+import { handleFormErrorsGeneric } from '@nemo/common/src/utils/forms/error';
import {
getEntitiesGetWorkspaceQueryKey,
getEntitiesListWorkspacesQueryKey,
@@ -15,7 +16,6 @@ import {
import { EntitiesUpdateWorkspaceBody } from '@nemo/sdk/generated/platform/zod/entity-store';
import { Stack, Text } from '@nvidia/foundations-react-core';
import { queryClient } from '@studio/api/queryClient';
-import { handleFormErrorsGeneric } from '@studio/util/forms/error';
import { FC, useEffect } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
diff --git a/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx b/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx
index 7a71a64d8c..1ecc1ce1ad 100644
--- a/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx
+++ b/web/packages/studio/src/routes/WorkspaceSettingsRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { DEFAULT_WORKSPACE } from '@nemo/common/src/models/constants';
import {
Button,
@@ -11,7 +12,6 @@ import {
Stack,
Text,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge';
import {
INFERENCE_PROVIDER_ENABLED,
diff --git a/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx b/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx
index 0d418e4451..5b996e98a2 100644
--- a/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx
+++ b/web/packages/studio/src/routes/agents/AgentDetailRoute/DeploymentLogsView.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { LogViewer } from '@nemo/common/src/components/LogViewer';
+import { logger } from '@nemo/common/src/utils/logger';
import {
getAgentsStreamDeploymentLogsQueryKey,
useAgentsGetDeploymentLogs,
@@ -10,7 +11,6 @@ import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema';
import type { PlatformJobLog } from '@nemo/sdk/generated/platform/schema';
import { Block, Select, Stack, Text } from '@nvidia/foundations-react-core';
import { PLATFORM_BASE_URL } from '@studio/constants/environment';
-import { logger } from '@studio/util/logger';
import { streamSse } from '@studio/util/sseStream';
import { type FC, useEffect, useMemo, useState } from 'react';
import { useAuth } from 'react-oidc-context';
diff --git a/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx
index c2cdb66598..958a419701 100644
--- a/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/agents/AgentDetailRoute/index.tsx
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { StatusBadge } from '@nemo/common/src/components/StatusBadge';
import type { AgentDeployment } from '@nemo/sdk/generated/agents/schema/AgentDeployment';
import {
@@ -14,9 +16,7 @@ import {
TabsTrigger,
Text,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { getAgentModelNames } from '@studio/components/dataViews/AgentsDataView/utils';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { SubmitEvaluationModal } from '@studio/components/evaluation/SubmitEvaluationModal';
import { ROUTE_PARAMS } from '@studio/constants/routes';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx
index 6820847bf9..73faad3192 100644
--- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx
+++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationDetailRoute.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { AccordionPanel } from '@nemo/common/src/components/AccordionPanel';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
import { KVPair } from '@nemo/common/src/components/KVPair';
@@ -34,7 +35,6 @@ import {
joinBundleByTask,
parseBundleRef,
} from '@studio/api/evaluation/agent-evaluations';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { AgentEvalTaskResultsPanel } from '@studio/components/evaluation/AgentEvalTaskResultsPanel';
import { EvalAggregateScoresTable } from '@studio/components/evaluation/EvalAggregateScoresTable';
import { StatusLogsContent } from '@studio/components/evaluation/Jobs/StatusLogsContent';
diff --git a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx
index 880ab967cc..00a0b87810 100644
--- a/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx
+++ b/web/packages/studio/src/routes/agents/AgentEvaluationsRoute/AgentEvaluationsListRoute.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { AgentEvaluationsDataView } from '@studio/components/dataViews/AgentEvaluationsDataView';
import { SubmitEvaluationModal } from '@studio/components/evaluation/SubmitEvaluationModal';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx
index 1b300c48de..5050573ccf 100644
--- a/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx
+++ b/web/packages/studio/src/routes/agents/AgentMonitorRoute/index.tsx
@@ -1,10 +1,10 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { agentsListAgents } from '@nemo/sdk/generated/agents/api';
import { useFilesListFilesetFiles } from '@nemo/sdk/generated/platform/api';
import { Combobox, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { InferenceLogsTable } from '@studio/routes/agents/AgentMonitorRoute/components/InferenceLogsTable';
diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts
index a24036af6d..d763eb8c02 100644
--- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts
+++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/api.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { toError } from '@nemo/common/src/utils/logger';
import { customFetch } from '@nemo/sdk/generated/fetchers/platform';
import {
filesCreateFileset,
@@ -29,7 +30,6 @@ import {
serializeSuggestions,
suggestionIdentity,
} from '@studio/routes/agents/AgentSuggestionsRoute/utils';
-import { toError } from '@studio/util/logger';
export const TELEMETRY_FILESET = 'nemo-agent-telemetry';
export const OPTIMIZER_FILESET = 'nemo-agent-optimizer';
diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx
index 37a6bdd928..ee19dfaa28 100644
--- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx
+++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { ColumnFilterPanel } from '@nemo/common/src/components/DataView/ColumnFilterPanel';
import { FilterPanel } from '@nemo/common/src/components/DataView/FilterPanel';
import { Root as DataView } from '@nemo/common/src/components/DataView/internal';
@@ -16,7 +17,6 @@ import {
Text,
TextInput,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { Loading } from '@studio/components/Layouts/Loading';
import { AgentGroupSection } from '@studio/routes/agents/AgentSuggestionsRoute/components/AgentGroupSection';
import { ApplyEvalConfigModal } from '@studio/routes/agents/AgentSuggestionsRoute/components/ApplyEvalConfigModal';
diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts
index 863ce26c0d..94892fe236 100644
--- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts
+++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/useOptimizerSuggestions.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { toError } from '@nemo/common/src/utils/logger';
import { ensureEvalConfigFileset } from '@studio/api/evaluation/eval-config-fileset';
import {
applySuggestion,
@@ -37,7 +38,6 @@ import {
suggestionIdentity,
} from '@studio/routes/agents/AgentSuggestionsRoute/utils';
import { getAgentEvaluationDetailRoute } from '@studio/routes/utils';
-import { toError } from '@studio/util/logger';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useCallback, useEffect, useRef, useState } from 'react';
diff --git a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts
index 4f64e01f9c..2c33a0975f 100644
--- a/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts
+++ b/web/packages/studio/src/routes/agents/AgentSuggestionsRoute/utils.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import type { ModelEntity } from '@nemo/sdk/generated/platform/schema/ModelEntity';
import { SAMPLE_EVAL_CONFIG_PATH } from '@studio/api/evaluation/eval-config-fileset';
import type { AgentConfig } from '@studio/components/dataViews/AgentsDataView';
@@ -11,7 +12,6 @@ import type {
SnapshotShape,
SuggestionApplySpec,
} from '@studio/routes/agents/AgentSuggestionsRoute/types';
-import { logger } from '@studio/util/logger';
/** Fileset name for an agent's eval bundle. */
export const evalFilesetForAgent = (agentName: string): string => `${agentName}-eval`;
diff --git a/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx b/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx
index 3ceb49cbca..e2a2654113 100644
--- a/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx
+++ b/web/packages/studio/src/routes/agents/AgentsListRoute/CloneAgentModal/index.tsx
@@ -2,13 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledSearchableSelect } from '@nemo/common/src/components/form/ControlledSearchableSelect';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal } from '@nemo/common/src/components/FormModal';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { getAgentsListAgentsQueryKey, useAgentsCreateAgent } from '@nemo/sdk/generated/agents/api';
import { useModelsListModels } from '@nemo/sdk/generated/platform/api';
-import { getErrorMessage } from '@studio/api/common/utils';
import { DEFAULT_LARGE_PAGE_SIZE } from '@studio/constants/constants';
import {
applyModelToConfig,
diff --git a/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx b/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx
index 16034c8dcb..fbe7d27db8 100644
--- a/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx
+++ b/web/packages/studio/src/routes/agents/AgentsListRoute/CreateExampleAgentModal/index.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledSearchableSelect } from '@nemo/common/src/components/form/ControlledSearchableSelect';
import { ControlledSelect } from '@nemo/common/src/components/form/ControlledSelect';
import { FormModal } from '@nemo/common/src/components/FormModal';
@@ -9,7 +10,6 @@ import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { getAgentsListAgentsQueryKey, useAgentsCreateAgent } from '@nemo/sdk/generated/agents/api';
import { useModelsListModels } from '@nemo/sdk/generated/platform/api';
import { loadSampleAgentConfig } from '@studio/api/agents/loadSampleAgentConfig';
-import { getErrorMessage } from '@studio/api/common/utils';
import { DEFAULT_LARGE_PAGE_SIZE } from '@studio/constants/constants';
import {
buildSampleAgentName,
diff --git a/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx b/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx
index f4f5f75997..30d1bf07fa 100644
--- a/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx
+++ b/web/packages/studio/src/routes/agents/AgentsListRoute/index.tsx
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import type { Agent } from '@nemo/sdk/generated/agents/schema/Agent';
import { Button, PageHeader, Stack, Text } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { AgentsTable, type AgentTableRow } from '@studio/components/dataViews/AgentsDataView';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import {
diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx b/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx
index c627d90525..718aced993 100644
--- a/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx
+++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/ChatThreadErrorBoundary.tsx
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
+import { logger } from '@nemo/common/src/utils/logger';
import { Button } from '@nvidia/foundations-react-core';
-import { logger } from '@studio/util/logger';
import { Component, type ErrorInfo, type ReactNode } from 'react';
interface ChatThreadErrorBoundaryProps {
diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx b/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx
index f42e564f7b..d05370ab06 100644
--- a/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx
+++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/historyPanel/HistoryPanelContents.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import { Banner, Button, Flex, Text, Tooltip } from '@nvidia/foundations-react-core';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { Empty } from '@studio/components/Empty';
import {
deleteCopilotSessionHistory,
diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx b/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx
index d571dafa76..18db409ecc 100644
--- a/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx
+++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Banner, Stack, Text } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { useCopilotChatContext } from '@studio/routes/agents/CopilotChatRoute/context/useCopilotChatContext';
diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts
index 0679bc9816..aa2bc7630a 100644
--- a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts
+++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.test.ts
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { COPILOT_JOB_PROGRESS_MCP_TOOL_NAME } from '@studio/routes/agents/CopilotChatRoute/jobProgressConsts';
import {
getAssistantPartsFromCopilotEvent,
@@ -9,7 +10,6 @@ import {
parseSseChunk,
} from '@studio/routes/agents/CopilotChatRoute/stream';
import { COPILOT_SUBTLE_TOOL_GROUP_NAME } from '@studio/routes/agents/CopilotChatRoute/toolParts';
-import { logger } from '@studio/util/logger';
describe('Copilot stream utilities', () => {
it('parses SSE events and preserves incomplete trailing data', () => {
diff --git a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts
index e897804080..6d20f869b5 100644
--- a/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts
+++ b/web/packages/studio/src/routes/agents/CopilotChatRoute/stream.ts
@@ -2,11 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
import type { ThreadAssistantMessagePart } from '@assistant-ui/react';
+import { logger } from '@nemo/common/src/utils/logger';
import {
createCopilotToolCallPart,
groupConsecutiveCopilotSubtleToolCalls,
} from '@studio/routes/agents/CopilotChatRoute/toolParts';
-import { logger } from '@studio/util/logger';
interface ServerSentEvent {
event?: string;
diff --git a/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx b/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx
index 37236bb6ba..89fde821d3 100644
--- a/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx
+++ b/web/packages/studio/src/routes/evaluation/EvaluationLayout/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { DocumentationButton } from '@studio/components/DocumentationButton';
import { Loading } from '@studio/components/Layouts/Loading';
import { LINK_EVAL_DOCS_BENCHMARKS_INDUSTRY } from '@studio/constants/links';
diff --git a/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx b/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx
index 66e9ef02b8..020aac62ca 100644
--- a/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx
+++ b/web/packages/studio/src/routes/evaluation/EvaluationResultDetailsRoute/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { AccordionPanel } from '@nemo/common/src/components/AccordionPanel';
import { PlatformJobTerminalStatuses } from '@nemo/common/src/constants/query';
import { useEvaluatorGetEvaluateJob } from '@nemo/sdk/generated/evaluator/api';
@@ -15,7 +16,6 @@ import {
Spinner,
Stack,
} from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { EvalAggregateScoresTable } from '@studio/components/evaluation/EvalAggregateScoresTable';
import { DatasetEvalRowResultsPanel } from '@studio/components/evaluation/Jobs/datasetEval/DatasetEvalRowResultsPanel';
import { useDatasetEvalResults } from '@studio/components/evaluation/Jobs/datasetEval/useDatasetEvalResults';
diff --git a/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx b/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx
index a4ae4221b0..dff85e77a2 100644
--- a/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx
+++ b/web/packages/studio/src/routes/evaluation/EvaluationResultsLayout/index.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { Loading } from '@studio/components/Layouts/Loading';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { FC, Suspense, useEffect } from 'react';
diff --git a/web/packages/studio/src/routes/groups/agentRoutes.tsx b/web/packages/studio/src/routes/groups/agentRoutes.tsx
index b07163f8bb..1131d96b6b 100644
--- a/web/packages/studio/src/routes/groups/agentRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/agentRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { AGENTS_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { iconColorClass } from '@studio/routes/constants';
diff --git a/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx b/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx
index f94032a1a8..fb1c799aaf 100644
--- a/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/anonymizerRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ANONYMIZER_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateAnonymizerRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/customizationRoutes.tsx b/web/packages/studio/src/routes/groups/customizationRoutes.tsx
index d150cf5221..6166e18f7d 100644
--- a/web/packages/studio/src/routes/groups/customizationRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/customizationRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateCustomizationRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/dashboardRoutes.tsx b/web/packages/studio/src/routes/groups/dashboardRoutes.tsx
index 381f7b096e..b2e8476e49 100644
--- a/web/packages/studio/src/routes/groups/dashboardRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/dashboardRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { COPILOT_STUDIO_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateCopilotStudioRoutes, gateDashboardRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx b/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx
index 173de5646f..1d3cb71102 100644
--- a/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/dataDesignerRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { DATA_DESIGNER_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateDataDesignerRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/deploymentRoutes.tsx b/web/packages/studio/src/routes/groups/deploymentRoutes.tsx
index 30e6ce63ff..381e56a5f2 100644
--- a/web/packages/studio/src/routes/groups/deploymentRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/deploymentRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { DEPLOYMENTS_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateDeploymentsRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/evaluationRoutes.tsx b/web/packages/studio/src/routes/groups/evaluationRoutes.tsx
index da001535e1..909bd54880 100644
--- a/web/packages/studio/src/routes/groups/evaluationRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/evaluationRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateEvaluationBenchmarksRoutes, gateEvaluationRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/experimentRoutes.tsx b/web/packages/studio/src/routes/groups/experimentRoutes.tsx
index a34beba13e..73186a3fdc 100644
--- a/web/packages/studio/src/routes/groups/experimentRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/experimentRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateExperimentRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/filesetRoutes.tsx b/web/packages/studio/src/routes/groups/filesetRoutes.tsx
index fe65bc377e..1dfdb8c832 100644
--- a/web/packages/studio/src/routes/groups/filesetRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/filesetRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateDatasetsRoutes, gateFilesetDetailsRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx b/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx
index 135f12b6a0..a939766930 100644
--- a/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/guardrailsRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { GUARDRAIL_CHECKS_DEFAULT_SUB_TAB } from '@studio/routes/guardrails/GuardrailChecksTab/constants';
import { gateGuardrailsRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx b/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx
index 32f92d189f..1e8fedfe9a 100644
--- a/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/inferenceProviderRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateInferenceProviderRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/intakeRoutes.tsx b/web/packages/studio/src/routes/groups/intakeRoutes.tsx
index 3cf04d2f4e..b1399e6c09 100644
--- a/web/packages/studio/src/routes/groups/intakeRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/intakeRoutes.tsx
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { Stack } from '@nvidia/foundations-react-core';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { INTAKE_FILTER_ACTION_TARGET_ID } from '@studio/routes/IntakeLayout';
import { gateIntakeRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/jobRoutes.tsx b/web/packages/studio/src/routes/groups/jobRoutes.tsx
index ed05e9de2c..60f4f07693 100644
--- a/web/packages/studio/src/routes/groups/jobRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/jobRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateJobsRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/memberRoutes.tsx b/web/packages/studio/src/routes/groups/memberRoutes.tsx
index 66e40bd0c3..a0492267a8 100644
--- a/web/packages/studio/src/routes/groups/memberRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/memberRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateMembersRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx b/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx
index cf7b5114a6..610d23a4f3 100644
--- a/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/modelCompareRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { MODEL_COMPARE_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateModelCompareRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/optimizerRoutes.tsx b/web/packages/studio/src/routes/groups/optimizerRoutes.tsx
index 455addcab4..84be5c5126 100644
--- a/web/packages/studio/src/routes/groups/optimizerRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/optimizerRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { OPTIMIZER_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateOptimizerRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx b/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx
index 37948d7ff0..f9195be52e 100644
--- a/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/safeSynthesizerRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { SAFE_SYNTHESIZER_ENABLED } from '@studio/constants/environment';
import { ROUTES } from '@studio/constants/routes';
import { gateSafeSynthesizerRoutes } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/groups/secretsRoutes.tsx b/web/packages/studio/src/routes/groups/secretsRoutes.tsx
index 82201652ba..3b22f67ceb 100644
--- a/web/packages/studio/src/routes/groups/secretsRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/secretsRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateSecretsRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/settingsRoutes.tsx b/web/packages/studio/src/routes/groups/settingsRoutes.tsx
index 6c70030c87..f1f5c0097a 100644
--- a/web/packages/studio/src/routes/groups/settingsRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/settingsRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { gateSettingsRoutes } from '@studio/routes/utils';
import { lazy } from 'react';
diff --git a/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx b/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx
index 303cc67c09..781e187999 100644
--- a/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx
+++ b/web/packages/studio/src/routes/groups/virtualModelsRoutes.tsx
@@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { ROUTES } from '@studio/constants/routes';
import { lazy } from 'react';
import type { RouteObject } from 'react-router';
diff --git a/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx b/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx
index 5988ffdff7..ad632ce184 100644
--- a/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx
+++ b/web/packages/studio/src/routes/guardrails/CreateGuardrailModal/index.tsx
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { zodResolver } from '@hookform/resolvers/zod';
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { ControlledTextInput } from '@nemo/common/src/components/form/ControlledTextInput';
import { FormModal } from '@nemo/common/src/components/FormModal';
import { ENTITY_NAME_HELP, entityNameSchema, toCopyName } from '@nemo/common/src/utils/entityName';
@@ -11,7 +12,6 @@ import type {
GuardrailConfigInput,
GuardrailConfigInputData,
} from '@nemo/sdk/generated/platform/schema';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { getGuardrailDetailRoute } from '@studio/routes/utils';
import { useQueryClient } from '@tanstack/react-query';
diff --git a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx
index 613f0702eb..7e6839a0e1 100644
--- a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx
+++ b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCard.tsx
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import { Button, Flex, Panel, SegmentedControl, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useUpdateGuardrailCheck } from '@studio/api/guardrail-checks/hooks';
import type {
GuardrailCheckEntity,
diff --git a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx
index 82435f815f..f44fa68432 100644
--- a/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx
+++ b/web/packages/studio/src/routes/guardrails/GuardrailChecksTab/GuardrailTestCasesEditor.tsx
@@ -1,11 +1,11 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { LoadingButton } from '@nemo/common/src/components/LoadingButton';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
import type { RailsConfig } from '@nemo/sdk/generated/platform/schema';
import { Button, Flex, Stack, Tabs, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { useCreateGuardrailCheck, useRunGuardrailChecks } from '@studio/api/guardrail-checks/hooks';
import type { GuardrailCheckEntity } from '@studio/api/guardrail-checks/types';
import { GuardrailChecksDataView } from '@studio/components/dataViews/GuardrailChecksDataView';
diff --git a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx
index f8b932d538..4d4c175dc8 100644
--- a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx
+++ b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/GuardrailDetailActions.tsx
@@ -1,13 +1,13 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
-import { useGuardrailsDeleteConfig } from '@nemo/sdk/generated/platform/api';
-import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import {
type QuickActionItem,
QuickActionsMenuRoot,
-} from '@studio/components/QuickActionsMenu/QuickActionsMenuRoot';
+} from '@nemo/common/src/components/QuickActionsMenu/QuickActionsMenuRoot';
+import { useGuardrailsDeleteConfig } from '@nemo/sdk/generated/platform/api';
+import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { CreateGuardrailModal } from '@studio/routes/guardrails/CreateGuardrailModal';
import { getGuardrailsRoute } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx
index 53eca27220..a55367d0fb 100644
--- a/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx
+++ b/web/packages/studio/src/routes/guardrails/GuardrailDetailRoute/index.tsx
@@ -3,9 +3,9 @@
* SPDX-License-Identifier: Apache-2.0
*/
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { useGuardrailsGetGuardrailConfig } from '@nemo/sdk/generated/platform/api';
import { Flex, PageHeader, Stack, Tabs, Text } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { Loading } from '@studio/components/Layouts/Loading';
import { ROUTE_PARAMS, ROUTES } from '@studio/constants/routes';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
diff --git a/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx b/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx
index 909fe3f4ae..46fdf80c2d 100644
--- a/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx
+++ b/web/packages/studio/src/routes/guardrails/GuardrailsRoute/index.tsx
@@ -10,15 +10,15 @@
* its affiliates is strictly prohibited.
*/
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
+import { DeleteConfirmationModal } from '@nemo/common/src/components/DeleteConfirmationModal';
import {
getGuardrailsGetGuardrailConfigQueryKey,
useGuardrailsDeleteConfig,
} from '@nemo/sdk/generated/platform/api';
import type { GuardrailConfig } from '@nemo/sdk/generated/platform/schema';
import { Button, PageHeader, Stack } from '@nvidia/foundations-react-core';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
import { GuardrailsDataView } from '@studio/components/dataViews/GuardrailsDataView';
-import { DeleteConfirmationModal } from '@studio/components/DeleteConfirmationModal';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
import { CreateGuardrailModal } from '@studio/routes/guardrails/CreateGuardrailModal';
diff --git a/web/packages/studio/src/routes/index.tsx b/web/packages/studio/src/routes/index.tsx
index b54d11d538..c6d2b6d267 100644
--- a/web/packages/studio/src/routes/index.tsx
+++ b/web/packages/studio/src/routes/index.tsx
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { Loading } from '@studio/components/Layouts/Loading';
import { ROUTES } from '@studio/constants/routes';
import { PluginProvider } from '@studio/plugins/PluginProvider';
diff --git a/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx b/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx
index be8cf509ea..b5c0b6a421 100644
--- a/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx
+++ b/web/packages/studio/src/routes/optimizer/InsightTracesTable/index.tsx
@@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
import { withOperators } from '@nemo/common/src/api/filterOperators';
import { EditColumnsMenu } from '@nemo/common/src/components/DataView/internal';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
@@ -9,7 +10,6 @@ import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataView
import { useListTraces } from '@nemo/sdk/generated/platform/api';
import type { Trace, TraceFilter } from '@nemo/sdk/generated/platform/schema';
import { Flex, Stack, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { IntakeTelemetryDataView } from '@studio/components/IntakeLists/IntakeTelemetryDataView';
import { makeIntakeTraceColumns } from '@studio/components/IntakeLists/intakeTraceColumns';
import { getIntakeSessionTraceRoute } from '@studio/routes/utils';
diff --git a/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx b/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx
index 3a758188b7..46d59f5e00 100644
--- a/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx
+++ b/web/packages/studio/src/routes/optimizer/OptimizerInsightRoute/index.tsx
@@ -1,7 +1,9 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import { ErrorMessage } from '@nemo/common/src/components/ErrorMessage';
+import { ExpandableMessage } from '@nemo/common/src/components/ExpandableMessage';
import { KVPair } from '@nemo/common/src/components/KVPair';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { useToast } from '@nemo/common/src/providers/toast/useToast';
@@ -23,8 +25,6 @@ import {
useOptimizerUpdateInsight,
type InsightStatus,
} from '@studio/api/optimizer';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
-import { ExpandableMessage } from '@studio/components/ExpandableMessage';
import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge';
import { Loading } from '@studio/components/Layouts/Loading';
import { LINK_DOCS_STUDIO_EVALUATION } from '@studio/constants/links';
diff --git a/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx b/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx
index 1836ccc573..44c57d5f69 100644
--- a/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx
+++ b/web/packages/studio/src/routes/optimizer/OptimizerRoute/index.tsx
@@ -1,18 +1,18 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { getErrorMessage } from '@nemo/common/src/api/common/utils';
+import { AccessibleTitle } from '@nemo/common/src/components/AccessibleTitle';
import {
ROW_ACTIONS_COLUMN_SIZE,
StudioDataView,
} from '@nemo/common/src/components/DataView/StudioDataView';
+import { ErrorPanel } from '@nemo/common/src/components/ErrorPanel';
import { RelativeTime } from '@nemo/common/src/components/RelativeTime';
import { TableEmptyState } from '@nemo/common/src/components/TableEmptyState';
import { useStudioDataViewState } from '@nemo/common/src/hooks/useStudioDataViewState';
import { Flex, PageHeader, Stack, Tag, Text } from '@nvidia/foundations-react-core';
-import { getErrorMessage } from '@studio/api/common/utils';
import { type InsightListItem, useOptimizerListInsights } from '@studio/api/optimizer';
-import { AccessibleTitle } from '@studio/components/AccessibleTitle';
-import { ErrorPanel } from '@studio/components/ErrorPanel';
import { FeatureFlagBadge } from '@studio/components/FeatureFlagBadge';
import { useWorkspaceFromPath } from '@studio/hooks/useWorkspaceFromPath';
import { useBreadcrumbs } from '@studio/providers/breadcrumbs/useBreadcrumbs';
diff --git a/web/packages/studio/src/util/files.ts b/web/packages/studio/src/util/files.ts
index d34c6f60b7..f2b0d930cd 100644
--- a/web/packages/studio/src/util/files.ts
+++ b/web/packages/studio/src/util/files.ts
@@ -2,9 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
import { ContentType } from '@nemo/common/src/components/CodeEditor/constants';
+import { logger } from '@nemo/common/src/utils/logger';
import { FileSystemDirectory, FileSystemNode } from '@studio/components/FilesTable/utils';
import { parseCSVTable } from '@studio/components/SafeSynthesizerFilesetPreview/util';
-import { logger } from '@studio/util/logger';
import { getTextWithCount, parseCSV } from '@studio/util/strings';
/**
diff --git a/web/packages/studio/src/util/llm.ts b/web/packages/studio/src/util/llm.ts
index c81ce0ecc4..13c9b99102 100644
--- a/web/packages/studio/src/util/llm.ts
+++ b/web/packages/studio/src/util/llm.ts
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import type { ExcludedChatCompletionMessageParam } from '@nemo/common/src/types/chat';
+import { logger } from '@nemo/common/src/utils/logger';
import { Row } from '@studio/util/files';
-import { logger } from '@studio/util/logger';
import Handlebars from 'handlebars';
export const extractUserMessage = (props: { row: Row; template?: string }): string => {
diff --git a/web/packages/studio/src/util/logVersion.ts b/web/packages/studio/src/util/logVersion.ts
new file mode 100644
index 0000000000..7993faf4c2
--- /dev/null
+++ b/web/packages/studio/src/util/logVersion.ts
@@ -0,0 +1,11 @@
+// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { logger } from '@nemo/common/src/utils/logger';
+import { VERSION_SHA } from '@studio/constants/environment';
+
+export const logVersion = async () => {
+ if (VERSION_SHA) {
+ logger.info(`Version: ${VERSION_SHA}`);
+ }
+};
diff --git a/web/packages/studio/src/util/logger.test.ts b/web/packages/studio/src/util/logger.test.ts
index 6c95f19fa2..cc11e406a1 100644
--- a/web/packages/studio/src/util/logger.test.ts
+++ b/web/packages/studio/src/util/logger.test.ts
@@ -31,8 +31,9 @@ vi.mock('@studio/constants/environment', async (importOriginal) => {
};
});
+import { handleGenericError, logger } from '@nemo/common/src/utils/logger';
import { SeverityNumber } from '@opentelemetry/api-logs';
-import { handleGenericError, logVersion, logger } from '@studio/util/logger';
+import { logVersion } from '@studio/util/logVersion';
describe('WebsiteLogger', () => {
beforeEach(() => {
diff --git a/web/packages/studio/src/util/strings.ts b/web/packages/studio/src/util/strings.ts
index 6863c17fbb..ed869784ff 100644
--- a/web/packages/studio/src/util/strings.ts
+++ b/web/packages/studio/src/util/strings.ts
@@ -1,8 +1,8 @@
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
+import { logger } from '@nemo/common/src/utils/logger';
import { Row } from '@studio/util/files';
-import { logger } from '@studio/util/logger';
import Papa from 'papaparse';
export { getTextWithCount } from '@nemo/common/src/utils/formatters';
diff --git a/web/packages/studio/src/workers/LargeFileWorker.ts b/web/packages/studio/src/workers/LargeFileWorker.ts
index 9857c566a0..ecb0c74a25 100644
--- a/web/packages/studio/src/workers/LargeFileWorker.ts
+++ b/web/packages/studio/src/workers/LargeFileWorker.ts
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
import { DEFAULT_WORKSPACE } from '@nemo/common/src/models/constants';
+import { toError } from '@nemo/common/src/utils/logger';
import { filesDownloadFile } from '@nemo/sdk/generated/platform/api';
-import { toError } from '@studio/util/logger';
import axios from 'axios';
export interface LargeFileWorkerMessage {
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 6048d1a4cc..9149347d16 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -319,6 +319,9 @@ importers:
'@nvidia/foundations-react-core':
specifier: 'catalog:'
version: 1.7.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
+ '@opentelemetry/api-logs':
+ specifier: ^0.219.0
+ version: 0.219.0
'@tanstack/match-sorter-utils':
specifier: ^8.19.4
version: 8.19.4
@@ -443,6 +446,9 @@ importers:
p-limit:
specifier: 'catalog:'
version: 6.2.0
+ react-dropzone:
+ specifier: 'catalog:'
+ version: 14.4.1(react@19.2.7)
react-hook-form:
specifier: 'catalog:'
version: 7.71.2(react@19.2.7)