From e467394dc99833956f44900fc542c01e3310b040 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 22 Jun 2026 02:48:04 -0700 Subject: [PATCH] feat(dashboard): consistent ViewHeader on Skills/Mailbox/Agents; title metric on Missions - Skills, Mailbox, Agents migrated to the shared ViewHeader (existing controls passed through actions; Skills icon -> Zap to match sidebar). - Missions keeps its integrated header but adopts the shared 1.125rem title + size-20 icon. - ChatView intentionally left as-is (height-sensitive two-pane; documented with FNXC). - Tests updated for the new header markup (606 pass across the view suites). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dashboard/app/components/AgentsView.tsx | 17 ++-- .../dashboard/app/components/ChatView.tsx | 4 + .../dashboard/app/components/MailboxView.tsx | 96 ++++++++++--------- .../app/components/MissionManager.css | 6 +- .../app/components/MissionManager.tsx | 6 +- .../dashboard/app/components/SkillsView.tsx | 59 ++++++------ .../components/__tests__/MailboxView.test.tsx | 7 +- .../components/__tests__/SkillsView.test.tsx | 10 +- 8 files changed, 118 insertions(+), 87 deletions(-) diff --git a/packages/dashboard/app/components/AgentsView.tsx b/packages/dashboard/app/components/AgentsView.tsx index 78542d61a2..ce5d8f5baa 100644 --- a/packages/dashboard/app/components/AgentsView.tsx +++ b/packages/dashboard/app/components/AgentsView.tsx @@ -9,6 +9,7 @@ import { fetchAgents, updateAgent, updateAgentState, deleteAgent, startAgentRun, const AgentDetailView = lazy(() => import("./AgentDetailView").then((m) => ({ default: m.AgentDetailView }))); import { AgentTokenStatsPanel } from "./AgentTokenStatsPanel"; import { AgentsOverviewBar } from "./AgentsOverviewBar"; +import { ViewHeader } from "./ViewHeader"; import { AgentEmptyState } from "./AgentEmptyState"; import { useAgents } from "../hooks/useAgents"; import { useConfirm } from "../hooks/useConfirm"; @@ -1227,11 +1228,14 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin return (
-
-
- -

{t("agents.title", "Agents")}

-
+ {/* + FNXC:Navigation 2026-06-22-01:10: + Agents adopts the shared ViewHeader (Command Center-modeled) title row for cross-view consistency. The deeply-integrated controls (view-toggle, controls popup, refresh, import, new-agent) keep working by passing the existing agents-view-controls cluster through the header actions prop. The agents-view-controls / agents-view-primary-actions class names are preserved so existing scoped CSS (incl. mobile rules covered by the CSS string-match test) still applies. + */} +
-
+ } + /> {/* Sidebar */}
- {/* Header */} -
-
- - {t("mailbox.title", "Mailbox")} - {unreadCount > 0 && ( - - {unreadCount} - - )} -
-
- - {activeTab === "inbox" && unreadCount > 0 && ( + {/* + FNXC:Navigation 2026-06-22-01:10: + Mailbox adopts the shared ViewHeader (Command Center-modeled) for a consistent main-content title row. The unread count badge stays beside the title (preserving the mailbox-unread-badge test id), and Compose / Mark-all-read / Refresh controls move into the header actions cluster so they keep working. Tabs remain below the header as their own row. + */} + + {unreadCount > 0 && ( + + {unreadCount} + + )} - )} - -
-
+ {activeTab === "inbox" && unreadCount > 0 && ( + + )} + + + } + /> {/* Tabs */}
diff --git a/packages/dashboard/app/components/MissionManager.css b/packages/dashboard/app/components/MissionManager.css index 83fa01c864..586a8b111b 100644 --- a/packages/dashboard/app/components/MissionManager.css +++ b/packages/dashboard/app/components/MissionManager.css @@ -82,8 +82,12 @@ gap: var(--space-sm); } +/* +FNXC:Navigation 2026-06-22-01:10: +Title metric matches the shared ViewHeader (1.125rem) so the Missions header reads consistently with Command Center and the other normalized main-content views. +*/ .mission-manager__title { - font-size: var(--space-lg); + font-size: 1.125rem; font-weight: 600; margin: 0; color: var(--text); diff --git a/packages/dashboard/app/components/MissionManager.tsx b/packages/dashboard/app/components/MissionManager.tsx index 0b33d6b05c..7846456d9f 100644 --- a/packages/dashboard/app/components/MissionManager.tsx +++ b/packages/dashboard/app/components/MissionManager.tsx @@ -4815,6 +4815,10 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr aria-label={isInline ? undefined : t("missions.missionManagerAriaLabel", "Mission Manager")} data-testid="mission-manager-dialog" > + {/* + FNXC:Navigation 2026-06-22-01:10: + Missions keeps its own header element (not the shared ViewHeader component) because it owns a dynamic mobile title (mission title when one is selected), a back button for stacked list->detail nav, an inline-vs-modal padding variant, and the mission-header-title test id. To stay visually consistent with the Command Center-modeled ViewHeader, the title uses the same icon size (20) and 1.125rem title metric via .mission-manager__title. + */}
{selectedMission && ( @@ -4828,7 +4832,7 @@ export function MissionManager({ isOpen, isInline = false, onClose, addToast, pr )} - +

{t("missions.title", "Missions")} diff --git a/packages/dashboard/app/components/SkillsView.tsx b/packages/dashboard/app/components/SkillsView.tsx index 3f5df9e514..d79ddfcd4e 100644 --- a/packages/dashboard/app/components/SkillsView.tsx +++ b/packages/dashboard/app/components/SkillsView.tsx @@ -1,7 +1,8 @@ import "./SkillsView.css"; import { useCallback, useEffect, useRef, useState, type MouseEvent } from "react"; import { useTranslation } from "react-i18next"; -import { Wrench, RefreshCw, X, ChevronRight, ChevronDown, AlertCircle, Loader2 } from "lucide-react"; +import { Zap, RefreshCw, X, ChevronRight, ChevronDown, AlertCircle, Loader2 } from "lucide-react"; +import { ViewHeader } from "./ViewHeader"; import { fetchDiscoveredSkills, toggleExecutionSkill, @@ -233,34 +234,34 @@ export function SkillsView({ projectId, addToast, onClose }: SkillsViewProps) { return (
- {/* Header */} -
-
-

- - {t("skills.title", "Skills")} -

- {discoveredSkills.length} {t("skills.discovered", "discovered")} -
- -
- - -
-
+ {/* + FNXC:Navigation 2026-06-22-01:10: + Skills adopts the shared ViewHeader (Command Center-modeled) for a consistent main-content title row. Icon matches the left-sidebar nav (Zap). The discovered-count badge plus Close and Refresh controls move into the header actions cluster so they keep working. + */} + + {discoveredSkills.length} {t("skills.discovered", "discovered")} + + + + } + /> {/* Scrollable content area */}
diff --git a/packages/dashboard/app/components/__tests__/MailboxView.test.tsx b/packages/dashboard/app/components/__tests__/MailboxView.test.tsx index ebcdac7963..ef55a9f058 100644 --- a/packages/dashboard/app/components/__tests__/MailboxView.test.tsx +++ b/packages/dashboard/app/components/__tests__/MailboxView.test.tsx @@ -2013,9 +2013,12 @@ describe("MailboxView", () => { // Verify root element with data-testid expect(screen.getByTestId("mailbox-view")).toBeDefined(); - // Verify header - const header = container.querySelector(".mailbox-header"); + // FNXC:Navigation 2026-06-22-01:10: MailboxView migrated its bespoke + // .mailbox-header to the shared ViewHeader (.view-header) modeled after + // Command Center; assert the shared header element with the Mailbox title. + const header = container.querySelector(".view-header"); expect(header).toBeTruthy(); + expect(header?.querySelector(".view-header__title")?.textContent).toContain("Mailbox"); // Verify tabs const tabs = container.querySelector(".mailbox-tabs"); diff --git a/packages/dashboard/app/components/__tests__/SkillsView.test.tsx b/packages/dashboard/app/components/__tests__/SkillsView.test.tsx index 151160561e..31556bd7c0 100644 --- a/packages/dashboard/app/components/__tests__/SkillsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/SkillsView.test.tsx @@ -565,11 +565,17 @@ describe("SkillsView", () => { }); }); - it("renders .skills-view-header and .skills-view-content sections", async () => { + it("renders the shared ViewHeader and .skills-view-content sections", async () => { + // FNXC:Navigation 2026-06-22-01:10: SkillsView migrated its bespoke + // .skills-view-header to the shared ViewHeader (.view-header) modeled + // after Command Center; assert the shared header element and that the + // content sections still render below it. render(); await waitFor(() => { - expect(document.querySelector(".skills-view-header")).toBeTruthy(); + const header = document.querySelector(".view-header"); + expect(header).toBeTruthy(); + expect(header?.querySelector(".view-header__title")?.textContent).toContain("Skills"); expect(document.querySelector(".skills-view-section")).toBeTruthy(); }); });