diff --git a/packages/ui/src/cloud-ui/components/layout/dashboard-route-page.tsx b/packages/ui/src/cloud-ui/components/layout/dashboard-route-page.tsx
index 1a93f9bbc16d4..180e412c6575e 100644
--- a/packages/ui/src/cloud-ui/components/layout/dashboard-route-page.tsx
+++ b/packages/ui/src/cloud-ui/components/layout/dashboard-route-page.tsx
@@ -3,19 +3,15 @@
/**
* A dashboard route page that wires its header into the page-header context.
*/
-import {
- type ComponentPropsWithoutRef,
- type DependencyList,
- type ReactNode,
- useContext,
+import type {
+ ComponentPropsWithoutRef,
+ DependencyList,
+ ReactNode,
} from "react";
import { cn } from "../../lib/utils";
import { DashboardPageContainer, DashboardPageStack } from "./dashboard-page";
-import { PageHeaderProvider } from "./page-header-context";
-import {
- PageHeaderContext,
- useSetPageHeader,
-} from "./page-header-context.hooks";
+import { EnsurePageHeaderProvider } from "./page-header-context";
+import { useSetPageHeader } from "./page-header-context.hooks";
type DashboardRoutePageBannerTone = "info" | "success" | "warning" | "error";
@@ -71,14 +67,10 @@ function normalizeLayoutProps(
* chrome), it defers to that provider so the header stays visible to the chrome.
*/
export function DashboardRoutePage(props: DashboardRoutePageProps) {
- const hasAncestorProvider = useContext(PageHeaderContext) !== undefined;
- if (hasAncestorProvider) {
- return ;
- }
return (
-
+
-
+
);
}
diff --git a/packages/ui/src/cloud-ui/components/layout/index.ts b/packages/ui/src/cloud-ui/components/layout/index.ts
index 0fe4e496258f3..2c8d4e911ac82 100644
--- a/packages/ui/src/cloud-ui/components/layout/index.ts
+++ b/packages/ui/src/cloud-ui/components/layout/index.ts
@@ -41,7 +41,10 @@ export type {
DashboardSidebarLinkRenderProps,
DashboardSidebarSection,
} from "./dashboard-sidebar-types";
-export { PageHeaderProvider } from "./page-header-context";
+export {
+ EnsurePageHeaderProvider,
+ PageHeaderProvider,
+} from "./page-header-context";
export {
usePageHeader,
useSetPageHeader,
diff --git a/packages/ui/src/cloud-ui/components/layout/page-header-context.tsx b/packages/ui/src/cloud-ui/components/layout/page-header-context.tsx
index ec04ab8875e5e..bc7afc040a886 100644
--- a/packages/ui/src/cloud-ui/components/layout/page-header-context.tsx
+++ b/packages/ui/src/cloud-ui/components/layout/page-header-context.tsx
@@ -7,7 +7,7 @@
"use client";
-import { type ReactNode, useMemo, useState } from "react";
+import { type ReactNode, useContext, useMemo, useState } from "react";
import {
PageHeaderContext,
type PageHeaderInfo,
@@ -47,3 +47,30 @@ export function PageHeaderProvider({ children }: { children: ReactNode }) {
);
}
+
+/**
+ * Provide a page-header context ONLY when there isn't one already.
+ *
+ * Standalone cloud routes need their own {@link PageHeaderProvider} (mounted
+ * directly by `CloudRouterShell` / natively in the app, they have no ancestor
+ * provider and `useSetPageHeader` would throw). But the SAME routes also render
+ * inside `ConsoleShell`, which already provides one and reads it to draw the
+ * top-bar title. An unconditional inner provider SHADOWS the shell's, so
+ * `useSetPageHeader` writes to a dead context and the top bar shows no title
+ * (and any in-page heading then reads as a second, competing title).
+ *
+ * Wrapping a route body in this component defers to the shell's provider when
+ * present, and supplies its own otherwise — so the title always reaches
+ * whichever header is actually rendered.
+ */
+export function EnsurePageHeaderProvider({
+ children,
+}: {
+ children: ReactNode;
+}) {
+ const hasAncestorProvider = useContext(PageHeaderContext) !== undefined;
+ if (hasAncestorProvider) {
+ return <>{children}>;
+ }
+ return {children};
+}
diff --git a/packages/ui/src/cloud/analytics/Page.tsx b/packages/ui/src/cloud/analytics/Page.tsx
index d7af68f841bc5..8852cdbb09277 100644
--- a/packages/ui/src/cloud/analytics/Page.tsx
+++ b/packages/ui/src/cloud/analytics/Page.tsx
@@ -16,7 +16,7 @@ import { useSearchParams } from "react-router-dom";
import {
DashboardErrorState,
DashboardLoadingState,
- PageHeaderProvider,
+ EnsurePageHeaderProvider,
} from "../../cloud-ui";
import { useCloudT } from "../shell/CloudI18nProvider";
import { AnalyticsPageClient } from "./_components/analytics-page-client";
@@ -67,14 +67,17 @@ export default function AnalyticsPage() {
);
}
- // AnalyticsPageClient sets the page header; this standalone route has no
- // ancestor PageHeaderProvider, so supply one here.
+ // AnalyticsPageClient sets the page header. Inside the ConsoleShell its
+ // provider already exists and drives the top-bar title;
+ // EnsurePageHeaderProvider defers to it (supplying one only for the
+ // standalone/native mount) so the title reaches the shell header rather than
+ // a shadowed inner provider.
return (
-
+
-
+
);
}
diff --git a/packages/ui/src/cloud/instances/AgentsPage.tsx b/packages/ui/src/cloud/instances/AgentsPage.tsx
index f7be2f1c026f2..1ce5eeaf0c11c 100644
--- a/packages/ui/src/cloud/instances/AgentsPage.tsx
+++ b/packages/ui/src/cloud/instances/AgentsPage.tsx
@@ -74,18 +74,10 @@ export default function AgentsPage() {
return (
-
-
-
-
- {t("cloud.agents.eyebrow", { defaultValue: "Instances" })}
-
-
-
- {t("cloud.agents.title", { defaultValue: "Instances" })}
-
-
-
+ {/* Page title is surfaced in the console top bar by
+ ElizaAgentsPageWrapper (DashboardRoutePage title="Instances" →
+ useSetPageHeader). No inline page-level heading here — a second
+ "Instances" title under the top bar read as a double title. */}
+
-
+
);
}
diff --git a/packages/ui/src/cloud/instances/components/my-agents.tsx b/packages/ui/src/cloud/instances/components/my-agents.tsx
index 841d2db0db422..7bfabe4602555 100644
--- a/packages/ui/src/cloud/instances/components/my-agents.tsx
+++ b/packages/ui/src/cloud/instances/components/my-agents.tsx
@@ -117,11 +117,15 @@ function AgentConsoleOverview({
})}
-
+ {/* Console hero heading. Demoted from h1 to h2: the page-level
+ title ("My Agent") is owned by the console top bar via
+ useSetPageHeader below, so a second h1 here read as a double
+ title. This stays the in-page section heading. */}
+
{t("cloud.myAgents.heading", {
defaultValue: "Administer and enter your running agent",
})}
-
+
{t("cloud.myAgents.subheading", {
defaultValue:
diff --git a/packages/ui/src/cloud/shell/ConsoleShell.test.tsx b/packages/ui/src/cloud/shell/ConsoleShell.test.tsx
index 6d94d30da0c65..952e0e777e094 100644
--- a/packages/ui/src/cloud/shell/ConsoleShell.test.tsx
+++ b/packages/ui/src/cloud/shell/ConsoleShell.test.tsx
@@ -23,7 +23,10 @@ vi.mock("../lib/use-session-auth", () => ({
}),
}));
-import { useSetPageHeader } from "../../cloud-ui/components/layout";
+import {
+ EnsurePageHeaderProvider,
+ useSetPageHeader,
+} from "../../cloud-ui/components/layout";
import { ConsoleShell } from "./ConsoleShell";
const NAV_HREFS = [
@@ -48,6 +51,26 @@ function TitledPage() {
return
body
;
}
+/**
+ * A standalone-route body: it publishes its own header inside
+ * EnsurePageHeaderProvider (the pattern MyAgentsPage / AnalyticsPage use so
+ * they also work mounted directly by CloudRouterShell). Inside ConsoleShell the
+ * provider must DEFER to the shell so the title reaches the top bar rather than
+ * a shadowed inner provider.
+ */
+function StandaloneTitledPage() {
+ return (
+
+
+
+ );
+}
+
+function TitledInner() {
+ useSetPageHeader({ title: "Standalone QA" });
+ return
body
;
+}
+
describe("ConsoleShell", () => {
afterEach(cleanup);
@@ -73,4 +96,38 @@ describe("ConsoleShell", () => {
expect(hrefs.has(href), `missing sidebar link ${href}`).toBe(true);
}
});
+
+ it("surfaces a standalone route's title in the top bar (EnsurePageHeaderProvider defers to the shell, no shadowed provider)", () => {
+ render(
+
+
+
+
+ ,
+ );
+
+ // The page's own EnsurePageHeaderProvider defers to the shell provider, so
+ // useSetPageHeader writes to the context the top bar reads. Exactly one
+ // page-level heading with the title — no shadowed/dead inner provider.
+ expect(screen.getByRole("heading", { name: "Standalone QA" })).toBeTruthy();
+ });
+
+ it("names the account-plumbing section 'Workspace', not 'Account' (no section title duplicating an item label)", () => {
+ render(
+
+
+
+
+ ,
+ );
+
+ // The section that holds Connectors/Account/Security/Organization is
+ // titled "Workspace" so it doesn't repeat the "Account" item label below
+ // it (the sidebar half of the double-title fix).
+ expect(screen.getByText("Workspace")).toBeTruthy();
+ // The "Account" nav item itself is unchanged.
+ expect(
+ screen.getByRole("link", { name: /Account/i }).getAttribute("href"),
+ ).toBe("/dashboard/account");
+ });
});
diff --git a/packages/ui/src/cloud/shell/ConsoleShell.tsx b/packages/ui/src/cloud/shell/ConsoleShell.tsx
index 9dac155334943..0f540deeb5163 100644
--- a/packages/ui/src/cloud/shell/ConsoleShell.tsx
+++ b/packages/ui/src/cloud/shell/ConsoleShell.tsx
@@ -111,7 +111,10 @@ const CONSOLE_NAV_SECTIONS: DashboardSidebarSection[] = [
],
},
{
- title: "Account",
+ // Section title was "Account", which duplicated the "Account" item label
+ // right below it (double-title feel in the sidebar). "Workspace" covers
+ // the whole group — connectors, account, security, and organization.
+ title: "Workspace",
items: [
{
id: "connectors",
diff --git a/packages/ui/src/i18n/locales/en.json b/packages/ui/src/i18n/locales/en.json
index 4d5a5017b8a2c..c10d196c4b645 100644
--- a/packages/ui/src/i18n/locales/en.json
+++ b/packages/ui/src/i18n/locales/en.json
@@ -785,11 +785,9 @@
"cloud.agents.detail.vpnIp": "VPN IP",
"cloud.agents.detail.webUi": "Web UI",
"cloud.agents.detail.webUiPort": "Web UI Port",
- "cloud.agents.eyebrow": "Instances",
"cloud.agents.loading": "Loading instances",
"cloud.agents.metaDescription": "View, launch, and manage your instances backed by Eliza Cloud.",
"cloud.agents.metaTitle": "Instances",
- "cloud.agents.title": "Instances",
"cloud.analytics.custom": "Custom",
"cloud.analytics.dataPointsCount": "{{n}} data points",
"cloud.analytics.filters.aggregation": "Aggregation",
diff --git a/packages/ui/src/i18n/locales/es.json b/packages/ui/src/i18n/locales/es.json
index a73ca902b8829..9ed8f5cabfb0b 100644
--- a/packages/ui/src/i18n/locales/es.json
+++ b/packages/ui/src/i18n/locales/es.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "IP VPN",
"cloud.agents.detail.webUi": "Web UI",
"cloud.agents.detail.webUiPort": "Puerto Web UI",
- "cloud.agents.eyebrow": "Instancias",
"cloud.agents.loading": "Cargando instancias",
"cloud.agents.metaDescription": "Mira, lanza y gestiona tus instancias respaldadas por Eliza Cloud.",
"cloud.agents.metaTitle": "Instancias",
- "cloud.agents.title": "Instancias",
"cloud.analytics.custom": "Personalizado",
"cloud.analytics.dataPointsCount": "{{n}} puntos de datos",
"cloud.analytics.filters.aggregation": "Agregación",
diff --git a/packages/ui/src/i18n/locales/ja.json b/packages/ui/src/i18n/locales/ja.json
index 6d10dba82ffa1..95e0fc546ce98 100644
--- a/packages/ui/src/i18n/locales/ja.json
+++ b/packages/ui/src/i18n/locales/ja.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "VPN IP",
"cloud.agents.detail.webUi": "Web UI",
"cloud.agents.detail.webUiPort": "Web UIポート",
- "cloud.agents.eyebrow": "インスタンス",
"cloud.agents.loading": "インスタンスを読み込み中",
"cloud.agents.metaDescription": "Eliza Cloudで動くインスタンスの確認・起動・管理。",
"cloud.agents.metaTitle": "インスタンス",
- "cloud.agents.title": "インスタンス",
"cloud.analytics.custom": "カスタム",
"cloud.analytics.dataPointsCount": "{{n}}件のデータポイント",
"cloud.analytics.filters.aggregation": "集計",
diff --git a/packages/ui/src/i18n/locales/ko.json b/packages/ui/src/i18n/locales/ko.json
index b061d8751b63f..249cffbc65721 100644
--- a/packages/ui/src/i18n/locales/ko.json
+++ b/packages/ui/src/i18n/locales/ko.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "VPN IP",
"cloud.agents.detail.webUi": "웹 UI",
"cloud.agents.detail.webUiPort": "Web UI 포트",
- "cloud.agents.eyebrow": "인스턴스",
"cloud.agents.loading": "인스턴스 불러오는 중",
"cloud.agents.metaDescription": "Eliza Cloud로 구동되는 인스턴스를 보고, 실행하고, 관리하세요.",
"cloud.agents.metaTitle": "인스턴스",
- "cloud.agents.title": "인스턴스",
"cloud.analytics.custom": "사용자 정의",
"cloud.analytics.dataPointsCount": "데이터 포인트 {{n}}개",
"cloud.analytics.filters.aggregation": "집계",
diff --git a/packages/ui/src/i18n/locales/pt.json b/packages/ui/src/i18n/locales/pt.json
index 6401d561ca86b..57792494183d3 100644
--- a/packages/ui/src/i18n/locales/pt.json
+++ b/packages/ui/src/i18n/locales/pt.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "IP VPN",
"cloud.agents.detail.webUi": "Interface Web",
"cloud.agents.detail.webUiPort": "Porta da Interface Web",
- "cloud.agents.eyebrow": "Instâncias",
"cloud.agents.loading": "Carregando instâncias",
"cloud.agents.metaDescription": "Visualize, inicie e gerencie suas instâncias com suporte do Eliza Cloud.",
"cloud.agents.metaTitle": "Instâncias",
- "cloud.agents.title": "Instâncias",
"cloud.analytics.custom": "Personalizado",
"cloud.analytics.dataPointsCount": "{{n}} pontos de dados",
"cloud.analytics.filters.aggregation": "Agregação",
diff --git a/packages/ui/src/i18n/locales/tl.json b/packages/ui/src/i18n/locales/tl.json
index e1eaa8b35bb9c..239d57ee3efde 100644
--- a/packages/ui/src/i18n/locales/tl.json
+++ b/packages/ui/src/i18n/locales/tl.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "VPN IP",
"cloud.agents.detail.webUi": "Web UI",
"cloud.agents.detail.webUiPort": "Port ng Web UI",
- "cloud.agents.eyebrow": "Mga instance",
"cloud.agents.loading": "Naglo-load ng instances",
"cloud.agents.metaDescription": "Tingnan, ilunsad, at i-manage ang instances mo sa Eliza Cloud.",
"cloud.agents.metaTitle": "Mga instance",
- "cloud.agents.title": "Mga instance",
"cloud.analytics.custom": "Pasadya",
"cloud.analytics.dataPointsCount": "{{n}} mga punto ng datos",
"cloud.analytics.filters.aggregation": "Aggregasyon",
diff --git a/packages/ui/src/i18n/locales/vi.json b/packages/ui/src/i18n/locales/vi.json
index f31b955c6b055..54f6b1c917a49 100644
--- a/packages/ui/src/i18n/locales/vi.json
+++ b/packages/ui/src/i18n/locales/vi.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "IP VPN",
"cloud.agents.detail.webUi": "Giao diện web",
"cloud.agents.detail.webUiPort": "Cổng giao diện web",
- "cloud.agents.eyebrow": "Phiên bản",
"cloud.agents.loading": "Đang tải instances",
"cloud.agents.metaDescription": "Xem, khởi chạy và quản lý instances của bạn trên Eliza Cloud.",
"cloud.agents.metaTitle": "Phiên bản",
- "cloud.agents.title": "Phiên bản",
"cloud.analytics.custom": "Tùy chỉnh",
"cloud.analytics.dataPointsCount": "{{n}} điểm dữ liệu",
"cloud.analytics.filters.aggregation": "Tổng hợp",
diff --git a/packages/ui/src/i18n/locales/zh-CN.json b/packages/ui/src/i18n/locales/zh-CN.json
index 63bf7faa00abf..e67df7133625b 100644
--- a/packages/ui/src/i18n/locales/zh-CN.json
+++ b/packages/ui/src/i18n/locales/zh-CN.json
@@ -778,11 +778,9 @@
"cloud.agents.detail.vpnIp": "VPN IP",
"cloud.agents.detail.webUi": "Web UI",
"cloud.agents.detail.webUiPort": "Web UI 端口",
- "cloud.agents.eyebrow": "实例",
"cloud.agents.loading": "正在加载实例",
"cloud.agents.metaDescription": "查看、启动和管理由 Eliza Cloud 支持的实例。",
"cloud.agents.metaTitle": "实例",
- "cloud.agents.title": "实例",
"cloud.analytics.custom": "自定义",
"cloud.analytics.dataPointsCount": "{{n}} 个数据点",
"cloud.analytics.filters.aggregation": "聚合",