This is an automated email from the ASF dual-hosted git repository.

zqr10159 pushed a commit to branch 2.0.0
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git

commit e403551b18751a377b5159696e60c2164274241e
Author: Logic <[email protected]>
AuthorDate: Fri May 29 00:45:37 2026 +0800

    feat(web-next): restore overview dashboard parity
---
 .../app/overview/{page.tsx => overview-page.tsx}   | 139 ++++++-
 web-next/app/overview/page.test.tsx                | 173 ++++++++-
 web-next/app/overview/page.tsx                     | 416 +--------------------
 .../components/overview/overview-console.test.tsx  |  26 +-
 web-next/components/overview/overview-console.tsx  |  35 +-
 .../overview/overview-detail-dialog.test.tsx       |  26 ++
 .../components/overview/overview-detail-dialog.tsx |  33 +-
 web-next/lib/overview/navigation.test.ts           |  24 +-
 web-next/lib/overview/navigation.ts                |   9 +
 web-next/lib/overview/view-model.test.ts           |  62 +++
 web-next/lib/overview/view-model.ts                |  27 +-
 11 files changed, 496 insertions(+), 474 deletions(-)

diff --git a/web-next/app/overview/page.tsx 
b/web-next/app/overview/overview-page.tsx
similarity index 79%
copy from web-next/app/overview/page.tsx
copy to web-next/app/overview/overview-page.tsx
index c70cc0b294..deb0761bd4 100644
--- a/web-next/app/overview/page.tsx
+++ b/web-next/app/overview/overview-page.tsx
@@ -2,6 +2,7 @@
 
 import React from 'react';
 import Link from 'next/link';
+import { useQueryClient } from '@tanstack/react-query';
 import { StageSection, SupportPanel } from '@/components/observability';
 import { ClientWorkbench } from '@/components/workbench/client-workbench';
 import { useI18n } from '@/components/providers/i18n-provider';
@@ -19,35 +20,131 @@ import {
   type OverviewImpactedItem,
   type OverviewSummaryItem
 } from '@/components/overview/overview-console';
-import { apiMessageGet } from '@/lib/api-client';
+import { api } from '@/lib/api-facade';
 import { buildOverviewConsoleViewModel } from '@/lib/overview/view-model';
+import { queryKeys } from '@/lib/query-keys';
 import type { DashboardSummary, PageResult, SingleAlert } from '@/lib/types';
 import { OverviewDetailDialog } from 
'../../components/overview/overview-detail-dialog';
 import { OverviewProblemFocusDialog } from 
'../../components/overview/overview-problem-focus-dialog';
 import { ThreeSignalDeskShell } from 
'../../components/pages/three-signal-desk-shell';
 import { buildOverviewSignalDeskHref } from '../../lib/overview/navigation';
 
-type OverviewData = {
+export type OverviewData = {
   summary: DashboardSummary;
   alerts: PageResult<SingleAlert>;
+  summaryFailed?: boolean;
+  alertsFailed?: boolean;
 };
 
-function resolveProblemFocusBadgeVariant(severity: string) {
-  if (severity === 'critical' || severity === 'error') {
+export type OverviewRenderDataState = {
+  renderData: OverviewData;
+  nextReadyData: OverviewData | null;
+  retainedReadyData: boolean;
+};
+
+const OVERVIEW_SUMMARY_URL = '/summary';
+const OVERVIEW_ALERT_LIST_URL = 
'/alerts?pageIndex=0&pageSize=6&sort=gmtUpdate&order=desc';
+const OVERVIEW_ALERT_LIST_QUERY = { pageIndex: 0, pageSize: 6, sort: 
'gmtUpdate', order: 'desc' } as const;
+const OVERVIEW_SETTLED_CACHE_TTL_MS = 10_000;
+const EMPTY_OVERVIEW_SUMMARY: DashboardSummary = { apps: [] };
+const EMPTY_OVERVIEW_ALERTS: PageResult<SingleAlert> = {
+  content: [],
+  totalElements: 0,
+  pageIndex: OVERVIEW_ALERT_LIST_QUERY.pageIndex,
+  pageSize: OVERVIEW_ALERT_LIST_QUERY.pageSize
+};
+
+type OverviewRequestState<T> = {
+  data: T;
+  failed: boolean;
+};
+
+async function loadOverviewRequest<T>(read: () => Promise<T>, fallback: T): 
Promise<OverviewRequestState<T>> {
+  try {
+    return { data: await read(), failed: false };
+  } catch {
+    return { data: fallback, failed: true };
+  }
+}
+
+async function loadOverviewConsoleData(): Promise<OverviewData> {
+  const [summary, alerts] = await Promise.all([
+    loadOverviewRequest(() => api.overview.summary(), EMPTY_OVERVIEW_SUMMARY),
+    loadOverviewRequest(() => api.overview.alerts(OVERVIEW_ALERT_LIST_QUERY), 
EMPTY_OVERVIEW_ALERTS)
+  ]);
+
+  return {
+    summary: summary.data,
+    alerts: alerts.data,
+    summaryFailed: summary.failed,
+    alertsFailed: alerts.failed
+  };
+}
+
+export function hasOverviewReadyContext(data: OverviewData): boolean {
+  return (data.summary.apps || []).length > 0 || (data.alerts.content || 
[]).length > 0;
+}
+
+export function resolveOverviewRenderData(data: OverviewData, 
previousReadyData: OverviewData | null): OverviewRenderDataState {
+  if (hasOverviewReadyContext(data)) {
+    return {
+      renderData: data,
+      nextReadyData: data,
+      retainedReadyData: false
+    };
+  }
+
+  if (previousReadyData) {
+    return {
+      renderData: previousReadyData,
+      nextReadyData: previousReadyData,
+      retainedReadyData: true
+    };
+  }
+
+  return {
+    renderData: data,
+    nextReadyData: null,
+    retainedReadyData: false
+  };
+}
+
+function problemFocusBadgeVariant(severityTone: 'default' | 'success' | 
'warning' | 'danger') {
+  if (severityTone === 'danger') {
     return 'danger' as const;
   }
-  if (severity === 'healthy') {
+  if (severityTone === 'success') {
     return 'success' as const;
   }
-  return 'accent' as const;
+  if (severityTone === 'warning') {
+    return 'accent' as const;
+  }
+  return 'default' as const;
 }
 
 export default function OverviewPage() {
   const { t } = useI18n();
+  const queryClient = useQueryClient();
   const [refreshNonce, setRefreshNonce] = React.useState(0);
   const [problemFocusDialogOpen, setProblemFocusDialogOpen] = 
React.useState(false);
   const [selectedSummaryCard, setSelectedSummaryCard] = 
React.useState<OverviewSummaryItem | null>(null);
   const [selectedImpactedEntity, setSelectedImpactedEntity] = 
React.useState<OverviewImpactedItem | null>(null);
+  const lastReadyOverviewDataRef = React.useRef<OverviewData | null>(null);
+  const overviewCacheKey = React.useMemo(
+    () => ['overview', OVERVIEW_SUMMARY_URL, OVERVIEW_ALERT_LIST_URL, 
refreshNonce].join(':'),
+    [refreshNonce]
+  );
+  const load = React.useCallback(async (): Promise<OverviewData> => {
+    return queryClient.fetchQuery({
+      queryKey: queryKeys.overview.console({
+        summary: OVERVIEW_SUMMARY_URL,
+        alerts: OVERVIEW_ALERT_LIST_URL,
+        refreshNonce
+      }),
+      queryFn: loadOverviewConsoleData,
+      staleTime: 5000
+    });
+  }, [queryClient, refreshNonce]);
 
   function openProblemFocusDialog() {
     setSelectedSummaryCard(null);
@@ -70,18 +167,17 @@ export default function OverviewPage() {
   return (
     <ClientWorkbench
       key={refreshNonce}
-      load={async (): Promise<OverviewData> => {
-        const [summary, alerts] = await Promise.all([
-          apiMessageGet<DashboardSummary>('/summary'),
-          
apiMessageGet<PageResult<SingleAlert>>('/alerts?pageIndex=0&pageSize=6&sort=gmtUpdate&order=desc')
-        ]);
-        return { summary, alerts };
-      }}
+      load={load}
       loadingCopy={t('overview.loading')}
+      cacheKey={overviewCacheKey}
+      cacheSettledTtlMs={OVERVIEW_SETTLED_CACHE_TTL_MS}
     >
       {data => {
-        const apps = data.summary.apps || [];
-        const alerts = data.alerts.content || [];
+        const { renderData, nextReadyData, retainedReadyData } = 
resolveOverviewRenderData(data, lastReadyOverviewDataRef.current);
+        lastReadyOverviewDataRef.current = nextReadyData;
+        const apps = renderData.summary.apps || [];
+        const alerts = renderData.alerts.content || [];
+        const overviewReadPartiallyFailed = Boolean(data.summaryFailed || 
data.alertsFailed);
         const viewModel = buildOverviewConsoleViewModel(apps, alerts, t);
         const topAlert = alerts[0];
         const setupRoute = 
buildOverviewSignalDeskHref('/ingestion/otlp?signal=logs', topAlert);
@@ -188,7 +284,11 @@ export default function OverviewPage() {
                 </>
               }
               main={
-                <div className="grid gap-3">
+                <div
+                  className="grid gap-3"
+                  data-overview-request-fallback={overviewReadPartiallyFailed 
? 'angular-partial-request-fallback' : undefined}
+                  data-overview-stale-ready-retained={retainedReadyData ? 
'angular-has-ready-overview-retains-last-data' : undefined}
+                >
                 <OverviewStatusGrid
                   title={t('dashboard.home.status.title')}
                   description={t('dashboard.home.status.copy')}
@@ -229,7 +329,10 @@ export default function OverviewPage() {
                           chrome="plain"
                           compact
                           actions={
-                            <Badge 
variant={resolveProblemFocusBadgeVariant(viewModel.problemFocus.severity)}>
+                            <Badge
+                              
variant={problemFocusBadgeVariant(viewModel.problemFocus.severityTone)}
+                              
data-overview-problem-focus-severity-tone={viewModel.problemFocus.severityTone}
+                            >
                               {viewModel.problemFocus.severityLabel}
                             </Badge>
                           }
@@ -396,7 +499,7 @@ export default function OverviewPage() {
                 title={selectedImpactedEntity ? 
`${selectedImpactedEntity.name} ${selectedImpactedEntity.type}` : ''}
                 subtitle={t('dashboard.affected.drawer-subtitle')}
                 description={selectedImpactedEntity?.lastIssue || ''}
-                status={selectedImpactedEntity?.severity}
+                statusTone={selectedImpactedEntity?.severityTone}
                 statusLabel={selectedImpactedEntity?.severityLabel}
                 sections={
                   selectedImpactedEntity
diff --git a/web-next/app/overview/page.test.tsx 
b/web-next/app/overview/page.test.tsx
index 3454fecb1a..7861ccf4bb 100644
--- a/web-next/app/overview/page.test.tsx
+++ b/web-next/app/overview/page.test.tsx
@@ -1,3 +1,5 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
 import React from 'react';
 import { renderToStaticMarkup } from 'react-dom/server';
 import { beforeEach, describe, expect, it, vi } from 'vitest';
@@ -53,6 +55,7 @@ function createOverviewViewModel() {
       title: 'checkout latency spike',
       severity: 'critical',
       severityLabel: 'Critical',
+      severityTone: 'danger',
       entity: 'checkout',
       owner: 'Platform',
       summary: 'Latency high'
@@ -61,7 +64,7 @@ function createOverviewViewModel() {
       { label: 'Alert Trend', value: '1', insight: 'Check alert pressure.', 
tone: 'danger' }
     ],
     impactedEntities: [
-      { name: 'checkout', type: 'service', severity: 'critical', 
severityLabel: 'Critical', owner: 'Platform', status: 'impacted', statusLabel: 
'Impacted', lastIssue: 'Latency high' }
+      { name: 'checkout', type: 'service', severity: 'critical', 
severityLabel: 'Critical', severityTone: 'danger', owner: 'Platform', status: 
'impacted', statusLabel: 'Impacted', lastIssue: 'Latency high' }
     ],
     activityItems: [
       { title: 'checkout latency spike', detail: 'Platform · checkout', 
timestamp: '2026-04-16 22:00:00', tone: 'danger', tag: 'Firing' }
@@ -103,7 +106,10 @@ function createOverviewViewModel() {
 const mockState = vi.hoisted(() => ({
   lastLoad: null as null | (() => Promise<unknown>),
   clientData: createClientData(),
-  viewModel: createOverviewViewModel()
+  viewModel: createOverviewViewModel(),
+  queryClient: {
+    fetchQuery: vi.fn(async ({ queryFn }: { queryFn: () => Promise<unknown> }) 
=> queryFn())
+  }
 }));
 
 const apiMessageGet = vi.fn();
@@ -119,16 +125,28 @@ vi.mock('@/components/providers/i18n-provider', () => ({
 }));
 
 vi.mock('@/components/workbench/client-workbench', () => ({
-  ClientWorkbench: ({ children, load }: { children: (data: any) => 
React.ReactNode; load: () => Promise<unknown> }) => {
+  ClientWorkbench: ({
+    children,
+    load,
+    loadingCopy
+  }: {
+    children: (data: any) => React.ReactNode;
+    load: () => Promise<unknown>;
+    loadingCopy?: string;
+  }) => {
     mockState.lastLoad = load;
     return (
-      <div data-client-workbench="true">
+      <div data-client-workbench="true" data-loading-copy={loadingCopy}>
         {children(mockState.clientData)}
       </div>
     );
   }
 }));
 
+vi.mock('@tanstack/react-query', () => ({
+  useQueryClient: () => mockState.queryClient
+}));
+
 vi.mock('../../components/workbench/primitives', () => ({
   WorkbenchPanel: ({ as: Component = 'div', children, ...props }: any) => 
<Component {...props}>{children}</Component>
 }));
@@ -272,10 +290,27 @@ beforeEach(() => {
   mockState.lastLoad = null;
   mockState.clientData = createClientData();
   mockState.viewModel = createOverviewViewModel();
+  mockState.queryClient.fetchQuery.mockReset();
+  mockState.queryClient.fetchQuery.mockImplementation(async ({ queryFn }: { 
queryFn: () => Promise<unknown> }) => queryFn());
   apiMessageGet.mockReset();
 });
 
 describe('overview page', () => {
+  it('keeps overview remounts on a short settled cache window without 
bypassing refresh keys', () => {
+    const source = readFileSync(resolve(process.cwd(), 
'app/overview/overview-page.tsx'), 'utf8');
+
+    expect(source).toContain('OVERVIEW_SETTLED_CACHE_TTL_MS = 10_000');
+    
expect(source).toContain('cacheSettledTtlMs={OVERVIEW_SETTLED_CACHE_TTL_MS}');
+    expect(source).toContain('key={refreshNonce}');
+    expect(source).toContain('cacheKey={overviewCacheKey}');
+    expect(source).toContain('queryKeys.overview.console');
+    expect(source).toContain('api.overview.summary()');
+    expect(source).toContain('api.overview.alerts(OVERVIEW_ALERT_LIST_QUERY)');
+    expect(source).toContain('resolveOverviewRenderData(data, 
lastReadyOverviewDataRef.current)');
+    expect(source).toContain('data-overview-stale-ready-retained');
+    expect(source).not.toContain("import { apiMessageGet } from 
'@/lib/api-client'");
+  });
+
   it('renders the HertzBeat overview shell and keeps the expected data loader 
contract', async () => {
     apiMessageGet
       .mockResolvedValueOnce({ apps: [] })
@@ -287,6 +322,7 @@ describe('overview page', () => {
 
     expect(html).toContain('data-workspace-shell="true"');
     expect(html).toContain('data-workspace-shell-rail-width="wide"');
+    expect(html).toContain('data-loading-copy="Loading overview console"');
     expect(html).toContain('checkout latency spike');
     expect(html).toContain('Refresh');
     expect(html).toContain('Alerts');
@@ -317,6 +353,131 @@ describe('overview page', () => {
       ['/summary'],
       ['/alerts?pageIndex=0&pageSize=6&sort=gmtUpdate&order=desc']
     ]);
+    expect(mockState.queryClient.fetchQuery).toHaveBeenCalledWith(
+      expect.objectContaining({
+        queryKey: ['overview', 'console', {
+          summary: '/summary',
+          alerts: '/alerts?pageIndex=0&pageSize=6&sort=gmtUpdate&order=desc',
+          refreshNonce: 0
+        }],
+        staleTime: 5000
+      })
+    );
+  }, 60000);
+
+  it('keeps Angular forkJoin-style summary fallback when alerts still load', 
async () => {
+    apiMessageGet
+      .mockRejectedValueOnce(new Error('summary unavailable'))
+      .mockResolvedValueOnce({
+        content: [
+          {
+            id: 7,
+            fingerprint: 'alert-7',
+            content: 'collector timeout',
+            status: 'firing',
+            labels: { severity: 'warning', service: 'collector' }
+          }
+        ],
+        totalElements: 1,
+        pageIndex: 0,
+        pageSize: 6
+      });
+
+    const { default: OverviewPage } = await import('./page');
+    renderToStaticMarkup(<OverviewPage />);
+
+    await expect(mockState.lastLoad?.()).resolves.toMatchObject({
+      summary: { apps: [] },
+      alerts: { content: [{ fingerprint: 'alert-7' }] },
+      summaryFailed: true,
+      alertsFailed: false
+    });
+    expect(apiMessageGet.mock.calls).toEqual([
+      ['/summary'],
+      ['/alerts?pageIndex=0&pageSize=6&sort=gmtUpdate&order=desc']
+    ]);
+  }, 60000);
+
+  it('keeps Angular forkJoin-style alert fallback when summary still loads', 
async () => {
+    apiMessageGet
+      .mockResolvedValueOnce({
+        apps: [
+          { app: 'api', category: 'service', size: 2, availableSize: 2, 
unAvailableSize: 0, unManageSize: 0 }
+        ]
+      })
+      .mockRejectedValueOnce(new Error('alerts unavailable'));
+
+    const { default: OverviewPage } = await import('./page');
+    renderToStaticMarkup(<OverviewPage />);
+
+    await expect(mockState.lastLoad?.()).resolves.toMatchObject({
+      summary: { apps: [{ app: 'api' }] },
+      alerts: { content: [], totalElements: 0, pageIndex: 0, pageSize: 6 },
+      summaryFailed: false,
+      alertsFailed: true
+    });
+  }, 60000);
+
+  it('marks partial overview read fallback without changing the route shell', 
async () => {
+    mockState.clientData = {
+      ...createClientData(),
+      summaryFailed: true
+    };
+
+    const { default: OverviewPage } = await import('./page');
+    const html = renderToStaticMarkup(<OverviewPage />);
+
+    
expect(html).toContain('data-overview-request-fallback="angular-partial-request-fallback"');
+    expect(html).toContain('data-workspace-shell="true"');
+  }, 60000);
+
+  it('retains the previous ready overview data when a later refresh returns 
empty fallback data', async () => {
+    const { hasOverviewReadyContext, resolveOverviewRenderData } = await 
import('./overview-page');
+    const readyData = {
+      summary: {
+        apps: [
+          { app: 'checkout', category: 'service', size: 4, availableSize: 3, 
unAvailableSize: 1, unManageSize: 0 }
+        ]
+      },
+      alerts: {
+        content: [
+          {
+            id: 7,
+            fingerprint: 'alert-7',
+            content: 'collector timeout',
+            status: 'firing',
+            labels: { severity: 'warning', service: 'collector' }
+          }
+        ],
+        totalElements: 1,
+        pageIndex: 0,
+        pageSize: 6
+      }
+    };
+    const emptyFallbackData = {
+      summary: { apps: [] },
+      alerts: { content: [], totalElements: 0, pageIndex: 0, pageSize: 6 },
+      summaryFailed: true,
+      alertsFailed: true
+    };
+
+    expect(hasOverviewReadyContext(readyData)).toBe(true);
+    expect(hasOverviewReadyContext(emptyFallbackData)).toBe(false);
+    expect(resolveOverviewRenderData(readyData, null)).toEqual({
+      renderData: readyData,
+      nextReadyData: readyData,
+      retainedReadyData: false
+    });
+    expect(resolveOverviewRenderData(emptyFallbackData, readyData)).toEqual({
+      renderData: readyData,
+      nextReadyData: readyData,
+      retainedReadyData: true
+    });
+    expect(resolveOverviewRenderData(emptyFallbackData, null)).toEqual({
+      renderData: emptyFallbackData,
+      nextReadyData: null,
+      retainedReadyData: false
+    });
   }, 60000);
 
   it('keeps the populated overview rail focused on next-step guidance instead 
of rendering the duplicated workbench recap panel', async () => {
@@ -410,6 +571,7 @@ describe('overview page', () => {
           type: 'service',
           severity: 'healthy',
           severityLabel: 'Healthy',
+          severityTone: 'success',
           owner: 'Platform',
           status: 'healthy',
           statusLabel: 'Stable',
@@ -433,6 +595,7 @@ describe('overview page', () => {
         title: 'No issue needs attention right now',
         severity: 'healthy',
         severityLabel: 'Healthy',
+        severityTone: 'success',
         entity: 'Overall environment',
         owner: 'Platform on call',
         summary: 'There is no issue requiring immediate escalation right now. 
Start with the trend and affected items.'
@@ -468,6 +631,7 @@ describe('overview page', () => {
         title: 'No issue needs attention right now',
         severity: 'healthy',
         severityLabel: 'Healthy',
+        severityTone: 'success',
         entity: 'Overall environment',
         owner: 'Platform on call',
         summary: 'There is no issue requiring immediate escalation right now. 
Start with the trend and affected items.'
@@ -483,6 +647,7 @@ describe('overview page', () => {
           type: 'service',
           severity: 'warning',
           severityLabel: 'Warning',
+          severityTone: 'warning',
           owner: 'Platform',
           status: 'impacted',
           statusLabel: 'Impacted',
diff --git a/web-next/app/overview/page.tsx b/web-next/app/overview/page.tsx
index c70cc0b294..5fe10f88d6 100644
--- a/web-next/app/overview/page.tsx
+++ b/web-next/app/overview/page.tsx
@@ -1,417 +1,7 @@
-'use client';
-
 import React from 'react';
-import Link from 'next/link';
-import { StageSection, SupportPanel } from '@/components/observability';
-import { ClientWorkbench } from '@/components/workbench/client-workbench';
-import { useI18n } from '@/components/providers/i18n-provider';
-import { Badge } from '@/components/ui/badge';
-import { Button, buttonVariants } from '@/components/ui/button';
-import {
-  OverviewActivityTimeline,
-  OverviewChecklist,
-  OverviewGuidancePanel,
-  OverviewImpactedList,
-  OverviewQuickEntryGrid,
-  OverviewSectionAction,
-  OverviewStatusGrid,
-  OverviewSummaryGrid,
-  type OverviewImpactedItem,
-  type OverviewSummaryItem
-} from '@/components/overview/overview-console';
-import { apiMessageGet } from '@/lib/api-client';
-import { buildOverviewConsoleViewModel } from '@/lib/overview/view-model';
-import type { DashboardSummary, PageResult, SingleAlert } from '@/lib/types';
-import { OverviewDetailDialog } from 
'../../components/overview/overview-detail-dialog';
-import { OverviewProblemFocusDialog } from 
'../../components/overview/overview-problem-focus-dialog';
-import { ThreeSignalDeskShell } from 
'../../components/pages/three-signal-desk-shell';
-import { buildOverviewSignalDeskHref } from '../../lib/overview/navigation';
-
-type OverviewData = {
-  summary: DashboardSummary;
-  alerts: PageResult<SingleAlert>;
-};
-
-function resolveProblemFocusBadgeVariant(severity: string) {
-  if (severity === 'critical' || severity === 'error') {
-    return 'danger' as const;
-  }
-  if (severity === 'healthy') {
-    return 'success' as const;
-  }
-  return 'accent' as const;
-}
-
-export default function OverviewPage() {
-  const { t } = useI18n();
-  const [refreshNonce, setRefreshNonce] = React.useState(0);
-  const [problemFocusDialogOpen, setProblemFocusDialogOpen] = 
React.useState(false);
-  const [selectedSummaryCard, setSelectedSummaryCard] = 
React.useState<OverviewSummaryItem | null>(null);
-  const [selectedImpactedEntity, setSelectedImpactedEntity] = 
React.useState<OverviewImpactedItem | null>(null);
-
-  function openProblemFocusDialog() {
-    setSelectedSummaryCard(null);
-    setSelectedImpactedEntity(null);
-    setProblemFocusDialogOpen(true);
-  }
-
-  function openSummaryCardDialog(card: OverviewSummaryItem) {
-    setProblemFocusDialogOpen(false);
-    setSelectedImpactedEntity(null);
-    setSelectedSummaryCard(card);
-  }
-
-  function openImpactedEntityDialog(entity: OverviewImpactedItem) {
-    setProblemFocusDialogOpen(false);
-    setSelectedSummaryCard(null);
-    setSelectedImpactedEntity(entity);
-  }
-
-  return (
-    <ClientWorkbench
-      key={refreshNonce}
-      load={async (): Promise<OverviewData> => {
-        const [summary, alerts] = await Promise.all([
-          apiMessageGet<DashboardSummary>('/summary'),
-          
apiMessageGet<PageResult<SingleAlert>>('/alerts?pageIndex=0&pageSize=6&sort=gmtUpdate&order=desc')
-        ]);
-        return { summary, alerts };
-      }}
-      loadingCopy={t('overview.loading')}
-    >
-      {data => {
-        const apps = data.summary.apps || [];
-        const alerts = data.alerts.content || [];
-        const viewModel = buildOverviewConsoleViewModel(apps, alerts, t);
-        const topAlert = alerts[0];
-        const setupRoute = 
buildOverviewSignalDeskHref('/ingestion/otlp?signal=logs', topAlert);
-        const statusActionHref = '/entities';
-        const statusActionLabel = t('dashboard.home.status.action.entities');
-        const quickEntryItems = viewModel.quickEntryItems.map(item => ({
-          ...item,
-          route: buildOverviewSignalDeskHref(item.route, topAlert)
-        }));
-        const guidanceNextLinks = viewModel.guidanceNextLinks.map(item => ({
-          ...item,
-          route: buildOverviewSignalDeskHref(item.route, topAlert)
-        }));
-        const guidanceLinkLabels = new Set(guidanceNextLinks.map(item => 
item.label));
-        const condensedQuickEntryItems = quickEntryItems.filter(item => 
!guidanceLinkLabels.has(item.label));
-        const actionableImpactedEntities = viewModel.impactedEntities.filter(
-          item => item.status !== 'healthy' || item.severity !== 'healthy'
-        );
-        const healthyOverviewPlaceholder = !viewModel.showSetupGuide && 
viewModel.problemFocus.severity === 'healthy';
-        const statusItems = healthyOverviewPlaceholder
-          ? [
-              {
-                key: 'workspace' as const,
-                label: t('dashboard.home.status.workspace'),
-                value: t('dashboard.home.status.ready'),
-                ready: true
-              },
-              {
-                key: 'ingestion' as const,
-                label: t('dashboard.home.status.ingestion'),
-                value: t('dashboard.home.status.pending'),
-                ready: false
-              },
-              {
-                key: 'entities' as const,
-                label: t('dashboard.home.status.entities'),
-                value: t('dashboard.home.status.pending'),
-                ready: false
-              },
-              {
-                key: 'alerts' as const,
-                label: t('dashboard.home.status.alerts'),
-                value: t('dashboard.home.status.pending'),
-                ready: false
-              }
-            ]
-          : viewModel.workspaceStatusItems;
-        const railChecklistItems = healthyOverviewPlaceholder
-          ? viewModel.checklistItems.map(item => ({
-              ...item,
-              ready: false
-            }))
-          : viewModel.checklistItems;
-        const setupLikeGuidance = viewModel.showSetupGuide || 
healthyOverviewPlaceholder;
-        const railGuidanceHeadline = setupLikeGuidance
-          ? t('dashboard.guidance.setup.headline')
-          : viewModel.guidanceHeadline;
-        const railGuidanceDescription = setupLikeGuidance
-          ? t('dashboard.guidance.setup.description')
-          : viewModel.guidanceDescription;
-        const railGuidanceReasons = setupLikeGuidance
-          ? [
-              { label: t('dashboard.setup.status.logs'), value: 
t('dashboard.setup.status.pending') },
-              { label: t('dashboard.setup.status.traces'), value: 
t('dashboard.setup.status.pending') },
-              { label: t('dashboard.setup.status.metrics'), value: 
t('dashboard.setup.status.pending') }
-            ]
-          : viewModel.guidanceReasons;
-        const railGuidanceNextLinks = setupLikeGuidance
-          ? []
-          : guidanceNextLinks.map(item => ({
-              label: item.label,
-              description: item.description,
-              href: item.route
-            }));
-
-        return (
-          <>
-            <ThreeSignalDeskShell
-              kicker={t('dashboard.darkops.kicker')}
-              title={t('dashboard.darkops.title')}
-              subtitle={t('dashboard.darkops.subtitle')}
-              showRail
-              showFooter
-              railWidth="wide"
-              actions={
-                <>
-                  <Button
-                    size="sm"
-                    variant="default"
-                    onClick={() => {
-                      setProblemFocusDialogOpen(false);
-                      setSelectedSummaryCard(null);
-                      setSelectedImpactedEntity(null);
-                      setRefreshNonce(current => current + 1);
-                    }}
-                  >
-                    {t('dashboard.darkops.action.refresh')}
-                  </Button>
-                  {!viewModel.showSetupGuide && !healthyOverviewPlaceholder ? (
-                    <Link href="/alerts" className={buttonVariants({ variant: 
'primary', size: 'sm' })}>
-                      {t('dashboard.darkops.action.review-alerts')}
-                    </Link>
-                  ) : null}
-                </>
-              }
-              main={
-                <div className="grid gap-3">
-                <OverviewStatusGrid
-                  title={t('dashboard.home.status.title')}
-                  description={t('dashboard.home.status.copy')}
-                  items={statusItems}
-                  density={healthyOverviewPlaceholder ? 'compact' : 'default'}
-                  action={
-                    !viewModel.showSetupGuide && !healthyOverviewPlaceholder ? 
(
-                      <OverviewSectionAction href={statusActionHref} 
label={statusActionLabel} />
-                    ) : undefined
-                  }
-                />
-
-                {viewModel.showSetupGuide ? (
-                  <SupportPanel title={t('dashboard.empty.title')} 
subtitle={t('dashboard.empty.copy')} chrome="plain" expanded>
-                    <div className="text-[11px] font-semibold uppercase 
tracking-[0.08em] text-[var(--ops-text-tertiary)]">
-                      {t('dashboard.empty.title')}
-                    </div>
-                  </SupportPanel>
-                ) : (
-                  <>
-                    {healthyOverviewPlaceholder ? (
-                      <SupportPanel
-                        title={t('dashboard.empty.title')}
-                        subtitle={t('dashboard.empty.copy')}
-                        chrome="default"
-                        tone="default"
-                        expanded
-                      >
-                        <div className="min-h-[48px]" />
-                      </SupportPanel>
-                    ) : null}
-
-                    {!healthyOverviewPlaceholder ? (
-                      <>
-                        <StageSection
-                          title={viewModel.problemFocus.title}
-                          description={viewModel.problemFocus.summary}
-                          chrome="plain"
-                          compact
-                          actions={
-                            <Badge 
variant={resolveProblemFocusBadgeVariant(viewModel.problemFocus.severity)}>
-                              {viewModel.problemFocus.severityLabel}
-                            </Badge>
-                          }
-                        >
-                          <div className="text-[11px] font-semibold uppercase 
tracking-[0.08em] text-[var(--ops-text-tertiary)]">
-                            {t('dashboard.problem-focus.kicker')}
-                          </div>
-
-                          <div className="mt-3 flex flex-wrap gap-4">
-                            <div className="grid gap-1 border-r 
border-[var(--ops-border-color)] pr-4">
-                              <div className="text-[11px] font-semibold 
uppercase tracking-[0.06em] text-[var(--ops-text-tertiary)]">
-                                {t('dashboard.problem-focus.entity')}
-                              </div>
-                              <div className="text-[13px] font-semibold 
text-[var(--ops-text-primary)]">
-                                {viewModel.problemFocus.entity}
-                              </div>
-                            </div>
-                            <div className="grid gap-1">
-                              <div className="text-[11px] font-semibold 
uppercase tracking-[0.06em] text-[var(--ops-text-tertiary)]">
-                                {t('dashboard.problem-focus.owner')}
-                              </div>
-                              <div className="text-[13px] font-semibold 
text-[var(--ops-text-primary)]">
-                                {viewModel.problemFocus.owner}
-                              </div>
-                            </div>
-                          </div>
-
-                          <div className="mt-3 flex flex-wrap gap-2">
-                            <OverviewSectionAction
-                              onClick={openProblemFocusDialog}
-                              label={t('dashboard.problem-focus.open-context')}
-                              variant="primary"
-                            />
-                            <OverviewSectionAction
-                              href="/alerts"
-                              
label={t('dashboard.problem-focus.review-alerts')}
-                            />
-                          </div>
-                        </StageSection>
-
-                        <OverviewSummaryGrid
-                          items={viewModel.summaryCards}
-                          onSelect={openSummaryCardDialog}
-                        />
-                      </>
-                    ) : null}
-
-                    {!healthyOverviewPlaceholder ? (
-                      <OverviewQuickEntryGrid
-                        kicker={t('dashboard.quick-entry.kicker')}
-                        title={t('dashboard.quick-entry.title')}
-                        items={condensedQuickEntryItems}
-                      />
-                    ) : null}
 
-                    {!healthyOverviewPlaceholder && 
actionableImpactedEntities.length > 0 ? (
-                      <OverviewImpactedList
-                        kicker={t('dashboard.affected.kicker')}
-                        title={t('dashboard.affected.title')}
-                        action={
-                          <OverviewSectionAction
-                            href="/entities"
-                            label={t('dashboard.affected.browse-all')}
-                          />
-                        }
-                        items={actionableImpactedEntities}
-                        onOpenItem={openImpactedEntityDialog}
-                      />
-                    ) : null}
-                  </>
-                )}
-                </div>
-              }
-              rail={
-                <div className="grid gap-3">
-                <OverviewGuidancePanel
-                  headline={railGuidanceHeadline}
-                  description={railGuidanceDescription}
-                  density={healthyOverviewPlaceholder ? 'compact' : 'default'}
-                  compactReasons={healthyOverviewPlaceholder}
-                  reasonDensity={healthyOverviewPlaceholder ? 'compact' : 
'default'}
-                  primaryAction={
-                    setupLikeGuidance ? (
-                      <OverviewSectionAction
-                        href={setupRoute}
-                        label={t('dashboard.guidance.setup.action')}
-                        variant="primary"
-                      />
-                    ) : (
-                      <OverviewSectionAction
-                        onClick={openProblemFocusDialog}
-                        label={t('dashboard.problem-focus.open-context')}
-                        variant="primary"
-                      />
-                    )
-                  }
-                  secondaryAction={
-                    !setupLikeGuidance ? (
-                      <OverviewSectionAction
-                        href="/alerts"
-                        label={t('dashboard.problem-focus.review-alerts')}
-                      />
-                    ) : undefined
-                  }
-                  reasons={railGuidanceReasons}
-                  nextLinks={railGuidanceNextLinks}
-                  startLabel={t('workspace.guidance.start')}
-                  reasonsLabel={t('workspace.guidance.reasons')}
-                  nextLabel={t('workspace.guidance.next')}
-                />
+import OverviewPage from './overview-page';
 
-                <OverviewChecklist
-                  title={t('dashboard.setup.checklist.title')}
-                  items={railChecklistItems}
-                  density={healthyOverviewPlaceholder ? 'compact' : 'default'}
-                />
-                </div>
-              }
-              footer={
-                <OverviewActivityTimeline
-                  title={t('dashboard.activity.title')}
-                  items={viewModel.activityItems}
-                  emptyText={t('dashboard.activity.empty')}
-                />
-              }
-            />
-            {!viewModel.showSetupGuide ? (
-              <OverviewProblemFocusDialog
-                open={problemFocusDialogOpen}
-                onClose={() => setProblemFocusDialogOpen(false)}
-                kicker={t('dashboard.problem-focus.kicker')}
-                title={viewModel.problemFocus.title}
-                summary={viewModel.problemFocus.summary}
-                subtitle={`${viewModel.problemFocus.entity} · 
${viewModel.problemFocus.severityLabel}`}
-                ownerLabel={t('dashboard.problem-focus.owner')}
-                owner={viewModel.problemFocus.owner}
-                entityLabel={t('dashboard.problem-focus.entity')}
-                entity={viewModel.problemFocus.entity}
-                closeLabel={t('common.button.cancel')}
-              />
-            ) : null}
-            {!viewModel.showSetupGuide ? (
-              <OverviewDetailDialog
-                open={selectedSummaryCard !== null}
-                onClose={() => setSelectedSummaryCard(null)}
-                title={selectedSummaryCard?.label || ''}
-                subtitle={t('dashboard.summary.drawer-subtitle')}
-                description={selectedSummaryCard?.hint || ''}
-                sections={
-                  selectedSummaryCard
-                    ? [
-                        { label: t('dashboard.summary.value'), value: 
selectedSummaryCard.value },
-                        { label: t('dashboard.summary.delta'), value: 
selectedSummaryCard.delta }
-                      ]
-                    : []
-                }
-                closeLabel={t('common.button.cancel')}
-              />
-            ) : null}
-            {!viewModel.showSetupGuide ? (
-              <OverviewDetailDialog
-                open={selectedImpactedEntity !== null}
-                onClose={() => setSelectedImpactedEntity(null)}
-                title={selectedImpactedEntity ? 
`${selectedImpactedEntity.name} ${selectedImpactedEntity.type}` : ''}
-                subtitle={t('dashboard.affected.drawer-subtitle')}
-                description={selectedImpactedEntity?.lastIssue || ''}
-                status={selectedImpactedEntity?.severity}
-                statusLabel={selectedImpactedEntity?.severityLabel}
-                sections={
-                  selectedImpactedEntity
-                    ? [
-                        { label: t('dashboard.problem-focus.owner'), value: 
selectedImpactedEntity.owner },
-                        { label: t('dashboard.affected.status-label'), value: 
selectedImpactedEntity.statusLabel }
-                      ]
-                    : []
-                }
-                closeLabel={t('common.button.cancel')}
-              />
-            ) : null}
-          </>
-        );
-      }}
-    </ClientWorkbench>
-  );
+export default function OverviewRoutePage() {
+  return <OverviewPage />;
 }
diff --git a/web-next/components/overview/overview-console.test.tsx 
b/web-next/components/overview/overview-console.test.tsx
index 87d1634b0c..7d2e430616 100644
--- a/web-next/components/overview/overview-console.test.tsx
+++ b/web-next/components/overview/overview-console.test.tsx
@@ -60,6 +60,18 @@ describe('overview console primitives', () => {
       />
     );
 
+    const customGuidanceHtml = renderToStaticMarkup(
+      <OverviewGuidancePanel
+        headline="Next: preserve caller-owned labels"
+        description="Custom labels are still passed through by callers that 
own this workflow language."
+        reasons={[{ label: 'Source', value: 'Caller' }]}
+        nextLinks={[{ label: 'Review', href: '/overview' }]}
+        startLabel="Custom start"
+        reasonsLabel="Custom reasons"
+        nextLabel="Custom next"
+      />
+    );
+
     expect(summaryHtml).toContain('data-overview-summary-grid="true"');
     expect(summaryHtml).toContain('data-overview-summary-item="true"');
     expect(summaryHtml).toContain('data-overview-summary-item-chrome="flat"');
@@ -67,8 +79,11 @@ describe('overview console primitives', () => {
     expect(summaryHtml).toContain('Critical pressure is still active');
     expect(summaryHtml).not.toContain('rounded-[10px] border 
border-[var(--ops-border-color)]');
     expect(guidanceHtml).toContain('data-overview-guidance="true"');
+    expect(guidanceHtml).toContain('>Next<');
     expect(guidanceHtml).toContain('Next: work the most important issue 
first');
+    expect(guidanceHtml).toContain('>Reasons<');
     expect(guidanceHtml).toContain('Entities in scope');
+    expect(guidanceHtml).toContain('>After that<');
     expect(guidanceHtml).toContain('href="/log/manage"');
     
expect(compactGuidanceHtml).toContain('data-overview-guidance-reasons-layout="pill-row"');
     
expect(compactGuidanceHtml).toContain('data-overview-guidance-reasons-density="compact"');
@@ -96,6 +111,11 @@ describe('overview console primitives', () => {
     expect(compactGuidanceHtml).toContain('text-[10px] leading-[1.25]');
     expect(compactGuidanceHtml).toContain('Logs');
     expect(compactGuidanceHtml).toContain('Pending');
+    expect(customGuidanceHtml).toContain('Custom start');
+    expect(customGuidanceHtml).toContain('Custom reasons');
+    expect(customGuidanceHtml).toContain('Custom next');
+    expect(customGuidanceHtml).not.toContain('>Reasons<');
+    expect(customGuidanceHtml).not.toContain('>After that<');
   });
 
   it('switches summary cards and impacted rows to button-backed actions when 
overview needs drawer-first posture', () => {
@@ -125,6 +145,7 @@ describe('overview console primitives', () => {
             type: 'service',
             severity: 'critical',
             severityLabel: 'Critical',
+            severityTone: 'danger',
             owner: 'Platform',
             statusLabel: 'Impacted',
             lastIssue: 'Latency high'
@@ -140,6 +161,7 @@ describe('overview console primitives', () => {
     expect(impactedHtml).toContain('<button');
     expect(impactedHtml).toContain('type="button"');
     expect(impactedHtml).toContain('checkout');
+    
expect(impactedHtml).toContain('data-overview-impacted-severity-tone="danger"');
     expect(impactedHtml).not.toContain('href="/entities?app=checkout"');
   });
 
@@ -168,7 +190,7 @@ describe('overview console primitives', () => {
       <OverviewChecklist
         title="Next Steps"
         items={[
-          { key: 'logs', label: 'Review logs', ready: false },
+          { key: 'logs', label: 'Review logs', ready: true },
           { key: 'traces', label: 'Review traces', ready: false },
           { key: 'metrics', label: 'Review metrics', ready: false },
           { key: 'alerts', label: 'Create an alert', ready: false }
@@ -184,6 +206,8 @@ describe('overview console primitives', () => {
     expect(checklistHtml).toContain('gap-0 py-1');
     expect(checklistHtml).toContain('text-[10px] leading-[1.25]');
     expect(checklistHtml).toContain('text-[9px] leading-[1.2]');
+    expect(checklistHtml).toContain('>Ready<');
+    expect(checklistHtml).toContain('>Pending<');
     expect(checklistHtml).not.toContain('gap-1.5');
     expect(checklistHtml).not.toContain('py-1.5');
   });
diff --git a/web-next/components/overview/overview-console.tsx 
b/web-next/components/overview/overview-console.tsx
index 3654276c0b..e055bf912b 100644
--- a/web-next/components/overview/overview-console.tsx
+++ b/web-next/components/overview/overview-console.tsx
@@ -2,6 +2,7 @@
 
 import Link from 'next/link';
 import * as React from 'react';
+import { SUPPLEMENTAL_MESSAGES } from '../../lib/i18n-runtime-messages';
 import { WorkbenchPanel } from '../../components/workbench/primitives';
 import { Badge } from '../ui/badge';
 import { buttonVariants } from '../ui/button';
@@ -36,6 +37,7 @@ export type OverviewImpactedItem = {
   type: string;
   severity: string;
   severityLabel: string;
+  severityTone: Tone;
   owner: string;
   statusLabel: string;
   lastIssue: string;
@@ -111,6 +113,12 @@ function timelineDotClass(tone: Tone) {
   }
 }
 
+const DEFAULT_OVERVIEW_GUIDANCE_START_LABEL = 
SUPPLEMENTAL_MESSAGES['en-US']?.['overview.guidance.default.start'] ?? 
'overview.guidance.default.start';
+const DEFAULT_OVERVIEW_GUIDANCE_REASONS_LABEL = 
SUPPLEMENTAL_MESSAGES['en-US']?.['overview.guidance.default.reasons'] ?? 
'overview.guidance.default.reasons';
+const DEFAULT_OVERVIEW_GUIDANCE_NEXT_LABEL = 
SUPPLEMENTAL_MESSAGES['en-US']?.['overview.guidance.default.next'] ?? 
'overview.guidance.default.next';
+const OVERVIEW_CHECKLIST_READY_LABEL = 
SUPPLEMENTAL_MESSAGES['en-US']?.['overview.checklist.status.ready'] ?? 
'overview.checklist.status.ready';
+const OVERVIEW_CHECKLIST_PENDING_LABEL = 
SUPPLEMENTAL_MESSAGES['en-US']?.['overview.checklist.status.pending'] ?? 
'overview.checklist.status.pending';
+
 export function OverviewStatusGrid({
   title,
   description,
@@ -271,7 +279,10 @@ export function OverviewImpactedList({
                 <span className="text-[12px] 
text-[var(--ops-text-secondary)]">{item.type} · {item.owner}</span>
               </span>
               <span className="grid justify-items-end gap-1">
-                <span className={cn('text-[12px] font-semibold uppercase', 
toneTextClass(resolveSeverityTone(item.severity)))}>
+                <span
+                  className={cn('text-[12px] font-semibold uppercase', 
toneTextClass(item.severityTone))}
+                  data-overview-impacted-severity-tone={item.severityTone}
+                >
                   {item.severityLabel}
                 </span>
                 <span className="text-[12px] 
text-[var(--ops-text-secondary)]">{item.statusLabel}</span>
@@ -322,9 +333,9 @@ export function OverviewGuidancePanel({
   density = 'default',
   compactReasons = false,
   reasonDensity = 'default',
-  startLabel = 'Next',
-  reasonsLabel = 'Reasons',
-  nextLabel = 'After that'
+  startLabel = DEFAULT_OVERVIEW_GUIDANCE_START_LABEL,
+  reasonsLabel = DEFAULT_OVERVIEW_GUIDANCE_REASONS_LABEL,
+  nextLabel = DEFAULT_OVERVIEW_GUIDANCE_NEXT_LABEL
 }: {
   headline: string;
   description: string;
@@ -531,7 +542,7 @@ export function OverviewChecklist({
                 density === 'compact' ? 'text-[9px] leading-[1.2]' : 
'text-[11px] leading-[1.45]'
               )}
             >
-              {item.ready ? 'Ready' : 'Pending'}
+              {item.ready ? OVERVIEW_CHECKLIST_READY_LABEL : 
OVERVIEW_CHECKLIST_PENDING_LABEL}
             </div>
           </div>
         ))}
@@ -666,17 +677,3 @@ export function OverviewSectionAction({
     </Link>
   );
 }
-
-function resolveSeverityTone(severity: string): Tone {
-  switch (severity.toLowerCase()) {
-    case 'critical':
-    case 'error':
-      return 'danger';
-    case 'warning':
-      return 'warning';
-    case 'healthy':
-      return 'success';
-    default:
-      return 'default';
-  }
-}
diff --git a/web-next/components/overview/overview-detail-dialog.test.tsx 
b/web-next/components/overview/overview-detail-dialog.test.tsx
new file mode 100644
index 0000000000..3347d4bbcf
--- /dev/null
+++ b/web-next/components/overview/overview-detail-dialog.test.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import { describe, expect, it } from 'vitest';
+import { OverviewDetailDialog } from './overview-detail-dialog';
+
+describe('OverviewDetailDialog', () => {
+  it('renders the status badge tone supplied by the caller', () => {
+    const html = renderToStaticMarkup(
+      <OverviewDetailDialog
+        open
+        onClose={() => undefined}
+        title="checkout-service service"
+        subtitle="Affected item"
+        description="Latency threshold breached"
+        statusTone="danger"
+        statusLabel="Critical"
+        sections={[{ label: 'Owner', value: 'Platform' }]}
+        closeLabel="Close"
+      />
+    );
+
+    expect(html).toContain('data-overview-detail-dialog="true"');
+    expect(html).toContain('data-overview-detail-status-tone="danger"');
+    expect(html).toContain('Critical');
+  });
+});
diff --git a/web-next/components/overview/overview-detail-dialog.tsx 
b/web-next/components/overview/overview-detail-dialog.tsx
index c2c2ae5447..bb9049dc19 100644
--- a/web-next/components/overview/overview-detail-dialog.tsx
+++ b/web-next/components/overview/overview-detail-dialog.tsx
@@ -10,18 +10,19 @@ type OverviewDetailSection = {
   value: string;
 };
 
-function resolveBadgeVariant(severity?: string) {
-  switch (`${severity || ''}`.toLowerCase()) {
-    case 'critical':
-    case 'error':
-      return 'danger' as const;
-    case 'healthy':
-      return 'success' as const;
-    case 'warning':
-      return 'accent' as const;
-    default:
-      return 'default' as const;
+type OverviewDetailBadgeTone = 'default' | 'success' | 'warning' | 'danger';
+
+function overviewDetailBadgeVariant(tone: OverviewDetailBadgeTone) {
+  if (tone === 'danger') {
+    return 'danger' as const;
+  }
+  if (tone === 'success') {
+    return 'success' as const;
+  }
+  if (tone === 'warning') {
+    return 'accent' as const;
   }
+  return 'default' as const;
 }
 
 export function OverviewDetailDialog({
@@ -31,7 +32,7 @@ export function OverviewDetailDialog({
   title,
   subtitle,
   description,
-  status,
+  statusTone = 'default',
   statusLabel,
   sections,
   closeLabel
@@ -42,7 +43,7 @@ export function OverviewDetailDialog({
   title: string;
   subtitle: string;
   description: string;
-  status?: string;
+  statusTone?: OverviewDetailBadgeTone;
   statusLabel?: string;
   sections: OverviewDetailSection[];
   closeLabel: string;
@@ -68,7 +69,11 @@ export function OverviewDetailDialog({
             <div className="text-[12px] font-semibold uppercase 
tracking-[0.08em] text-[var(--ops-text-tertiary)]">{subtitle}</div>
             <div className="text-[13px] leading-[1.6] 
text-[var(--ops-text-secondary)]">{description}</div>
           </div>
-          {statusLabel ? <Badge 
variant={resolveBadgeVariant(status)}>{statusLabel}</Badge> : null}
+          {statusLabel ? (
+            <Badge variant={overviewDetailBadgeVariant(statusTone)} 
data-overview-detail-status-tone={statusTone}>
+              {statusLabel}
+            </Badge>
+          ) : null}
         </div>
         <div className="grid gap-3 sm:grid-cols-2">
           {sections.map(section => (
diff --git a/web-next/lib/overview/navigation.test.ts 
b/web-next/lib/overview/navigation.test.ts
index 355d041148..69fe58a81c 100644
--- a/web-next/lib/overview/navigation.test.ts
+++ b/web-next/lib/overview/navigation.test.ts
@@ -1,7 +1,7 @@
 import { readFileSync } from 'node:fs';
 import { resolve } from 'node:path';
 import { describe, expect, it } from 'vitest';
-import { buildOverviewSignalDeskHref } from './navigation';
+import { buildOverviewCompatRouteUrl, buildOverviewSignalDeskHref } from 
'./navigation';
 
 const topAlert = {
   labels: {
@@ -14,9 +14,29 @@ const topAlert = {
 } as const;
 
 describe('overview navigation', () => {
+  it('builds overview compatibility redirects with normalized machine query 
context', () => {
+    const target = buildOverviewCompatRouteUrl({
+      source: 'root',
+      serviceName: 'checkout',
+      returnTo: '/entities?returnLabel=Catalog',
+      returnLabel: 'Overview',
+      start: '1700000000000',
+      environment: ['prod', 'ignored']
+    });
+    const url = new URL(target, 'http://127.0.0.1');
+
+    expect(url.pathname).toBe('/overview');
+    expect(url.searchParams.get('source')).toBe('root');
+    expect(url.searchParams.get('serviceName')).toBe('checkout');
+    expect(url.searchParams.get('environment')).toBe('prod');
+    expect(url.searchParams.get('returnTo')).toBe('/entities');
+    expect(url.searchParams.get('returnLabel')).toBeNull();
+    expect(url.searchParams.get('start')).toBe('1700000000000');
+  });
+
   it('keeps overview and monitor-editor navigation machine-only without 
display-label fields', () => {
     const overviewNavigationSource = readFileSync(resolve(process.cwd(), 
'lib/overview/navigation.ts'), 'utf8');
-    const overviewPageSource = readFileSync(resolve(process.cwd(), 
'app/overview/page.tsx'), 'utf8');
+    const overviewPageSource = readFileSync(resolve(process.cwd(), 
'app/overview/overview-page.tsx'), 'utf8');
     const monitorEditorTestSource = readFileSync(resolve(process.cwd(), 
'lib/monitor-editor/navigation.test.ts'), 'utf8');
 
     expect(overviewNavigationSource).not.toContain('returnLabel: string');
diff --git a/web-next/lib/overview/navigation.ts 
b/web-next/lib/overview/navigation.ts
index 2fbe210233..d9c970f464 100644
--- a/web-next/lib/overview/navigation.ts
+++ b/web-next/lib/overview/navigation.ts
@@ -1,6 +1,11 @@
+import { buildCompatRedirectTarget, type SearchParamsRecord } from 
'../compat/search-params';
 import type { SingleAlert } from '../types';
 import { stripReturnLabelFromHref } from '../signal-route-context';
 
+export type { SearchParamsRecord };
+
+export const OVERVIEW_ROUTE = '/overview';
+
 const OVERVIEW_SIGNAL_DESK_PATHS = new Set([
   '/log/manage',
   '/trace/manage',
@@ -28,6 +33,10 @@ function setIfMissing(params: URLSearchParams, key: string, 
value: string | unde
   params.set(key, value);
 }
 
+export function buildOverviewCompatRouteUrl(searchParams?: SearchParamsRecord) 
{
+  return buildCompatRedirectTarget(OVERVIEW_ROUTE, searchParams);
+}
+
 export function buildOverviewSignalDeskHref(
   href: string,
   alert: Pick<SingleAlert, 'labels'> | null | undefined
diff --git a/web-next/lib/overview/view-model.test.ts 
b/web-next/lib/overview/view-model.test.ts
index cd0dd09b6d..f960fc87ad 100644
--- a/web-next/lib/overview/view-model.test.ts
+++ b/web-next/lib/overview/view-model.test.ts
@@ -7,6 +7,7 @@ import {
 import { createTranslatorMock } from '../../test/i18n-test-helper';
 
 const t = createTranslatorMock({ locale: 'zh-CN' });
+const enT = createTranslatorMock({ locale: 'en-US' });
 
 describe('overview view model', () => {
   it('builds entity and alert metrics from app summary data', () => {
@@ -51,6 +52,61 @@ describe('overview view model', () => {
     expect(lanes.find(item => item.href === '/log/manage')?.stat).toBe('无链路 ID 
时优先');
   });
 
+  it('localizes overview investigation lane titles, copy, stats, and actions', 
() => {
+    const lanes = buildInvestigationLanes(
+      {
+        totalEntities: 7,
+        appCount: 2,
+        topAlertHasTraceId: false
+      },
+      t
+    );
+
+    expect(lanes.find(item => item.href === '/entities')).toMatchObject({
+      title: '实体目录',
+      eyebrow: '实体优先',
+      copy: '先从监控实体和服务归属定位影响范围,再进入信号排查。',
+      stat: '已纳管 7 个实体',
+      action: '打开实体目录'
+    });
+    expect(lanes.find(item => item.href === '/log/manage')).toMatchObject({
+      title: '日志',
+      eyebrow: '运行证据',
+      copy: '用相同服务、实体和时间上下文查看日志事件。',
+      stat: '日志可用',
+      action: '打开日志'
+    });
+    expect(lanes.find(item => item.href === '/trace/manage')).toMatchObject({
+      title: '链路',
+      eyebrow: '请求路径',
+      copy: '存在链路上下文时跟进跨度,再回到实体证据。',
+      stat: '分布式链路就绪',
+      action: '打开链路'
+    });
+    expect(lanes.find(item => item.href === 
'/ingestion/otlp/metrics')).toMatchObject({
+      title: 'OTLP 指标',
+      eyebrow: '三信号接入',
+      copy: '在私有部署的 HertzBeat 工作区查看进入的指标序列。',
+      stat: '已接入 2 个监控应用',
+      action: '打开指标'
+    });
+  });
+
+  it('keeps English log lane recommendation copy in English', () => {
+    const lanes = buildInvestigationLanes(
+      {
+        totalEntities: 7,
+        appCount: 2,
+        topAlertHasTraceId: true
+      },
+      enT
+    );
+    const logStat = lanes.find(item => item.href === '/log/manage')?.stat;
+
+    expect(logStat).toBe('Prioritize when trace ID is missing');
+    expect(logStat).not.toMatch(/[\u4e00-\u9fff]/);
+  });
+
   it('builds the HertzBeat overview console structure from app and alert 
data', () => {
     const viewModel = buildOverviewConsoleViewModel(
       [
@@ -87,8 +143,14 @@ describe('overview view model', () => {
     expect(viewModel.problemFocus).toMatchObject({
       title: 'checkout latency spike',
       severity: 'critical',
+      severityTone: 'danger',
       owner: 'Platform'
     });
+    expect(viewModel.impactedEntities[0]).toMatchObject({
+      name: 'checkout',
+      severity: 'critical',
+      severityTone: 'danger'
+    });
     expect(viewModel.quickEntryItems.map(item => item.route)).toEqual([
       '/entities',
       '/log/manage',
diff --git a/web-next/lib/overview/view-model.ts 
b/web-next/lib/overview/view-model.ts
index 7305e48e76..9a959c1129 100644
--- a/web-next/lib/overview/view-model.ts
+++ b/web-next/lib/overview/view-model.ts
@@ -2,6 +2,8 @@ import type { AlertSummary, AppCount, SingleAlert } from 
'@/lib/types';
 
 type Translator = (key: string, params?: Record<string, string | number | null 
| undefined>) => string;
 
+type OverviewTone = 'default' | 'success' | 'warning' | 'danger';
+
 type OverviewMetrics = {
   totalEntities: number;
   healthyEntities: number;
@@ -27,13 +29,14 @@ type OverviewSummaryCard = {
   value: string;
   hint: string;
   delta: string;
-  tone: 'default' | 'success' | 'warning' | 'danger';
+  tone: OverviewTone;
 };
 
 type OverviewProblemFocus = {
   title: string;
   severity: string;
   severityLabel: string;
+  severityTone: OverviewTone;
   entity: string;
   owner: string;
   summary: string;
@@ -43,7 +46,7 @@ type OverviewTrendCard = {
   label: string;
   value: string;
   insight: string;
-  tone: 'default' | 'success' | 'warning' | 'danger';
+  tone: OverviewTone;
 };
 
 type OverviewImpactedEntity = {
@@ -51,6 +54,7 @@ type OverviewImpactedEntity = {
   type: string;
   severity: string;
   severityLabel: string;
+  severityTone: OverviewTone;
   owner: string;
   status: string;
   statusLabel: string;
@@ -61,7 +65,7 @@ type OverviewActivityItem = {
   title: string;
   detail: string;
   timestamp: string;
-  tone: 'default' | 'success' | 'warning' | 'danger';
+  tone: OverviewTone;
   tag: string;
 };
 
@@ -332,6 +336,7 @@ function buildProblemFocus(alerts: SingleAlert[], t: 
Translator): OverviewProble
       title: t('dashboard.problem-focus.empty.title'),
       severity: 'healthy',
       severityLabel: resolveSeverityLabel('healthy', t),
+      severityTone: overviewSeverityTone('healthy'),
       entity: t('dashboard.problem-focus.empty.entity'),
       owner: t('dashboard.problem-focus.empty.owner'),
       summary: t('dashboard.problem-focus.empty.summary')
@@ -343,6 +348,7 @@ function buildProblemFocus(alerts: SingleAlert[], t: 
Translator): OverviewProble
     title: focus.content || focus.annotations?.summary || 
t('dashboard.problem-focus.default-title'),
     severity,
     severityLabel: resolveSeverityLabel(severity, t),
+    severityTone: overviewSeverityTone(severity),
     entity: focus.labels?.service || focus.labels?.job || 
focus.labels?.instance || t('dashboard.problem-focus.default-entity'),
     owner: getAlertOwnerLabel(focus, t),
     summary: focus.annotations?.summary || focus.content || 
t('dashboard.problem-focus.default-summary')
@@ -401,6 +407,7 @@ function buildImpactedEntities(appCounts: AppCount[], 
alerts: SingleAlert[], t:
         type: item.category || 'service',
         severity,
         severityLabel: resolveSeverityLabel(severity, t),
+        severityTone: overviewSeverityTone(severity),
         owner: getAlertOwnerLabel(linkedAlert, t),
         status: degraded > 0 ? 'impacted' : 'healthy',
         statusLabel: degraded > 0
@@ -451,6 +458,20 @@ function getAlertSeverity(alert?: SingleAlert) {
   return `${alert?.labels?.severity || alert?.annotations?.severity || 
'warning'}`.toLowerCase();
 }
 
+function overviewSeverityTone(severity: string): OverviewTone {
+  switch (severity.toLowerCase()) {
+    case 'critical':
+    case 'error':
+      return 'danger';
+    case 'warning':
+      return 'warning';
+    case 'healthy':
+      return 'success';
+    default:
+      return 'default';
+  }
+}
+
 function getAlertOwnerLabel(alert: SingleAlert | undefined, t: Translator) {
   return alert?.labels?.owner || alert?.annotations?.owner || 
t('dashboard.owner.unassigned');
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to