diff --git a/packages/dashboard/app/components/DocumentsView.css b/packages/dashboard/app/components/DocumentsView.css
index b7d53ccc68..2a42034ff4 100644
--- a/packages/dashboard/app/components/DocumentsView.css
+++ b/packages/dashboard/app/components/DocumentsView.css
@@ -9,33 +9,15 @@
min-height: 0;
}
+/*
+FNXC:Navigation 2026-06-22-01:10:
+The header row now comes from the shared .view-header (which supplies the --space-lg top/side padding). The container only contributes the bottom border + surface; the controls row gets its own side/bottom padding so the tab bar/search stay aligned without doubling the ViewHeader inset.
+*/
.documents-view-header {
- padding: var(--space-lg);
border-bottom: 1px solid var(--border);
background: var(--surface);
}
-.documents-view-title-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: var(--space-md);
-}
-
-.documents-view-title {
- display: flex;
- align-items: center;
- gap: var(--space-sm);
- font-size: 18px;
- font-weight: 600;
- margin: 0;
- color: var(--text);
-}
-
-.documents-view-title svg {
- color: var(--todo);
-}
-
.documents-view-count {
font-size: 14px;
color: var(--text-muted);
@@ -46,6 +28,7 @@
align-items: center;
gap: var(--space-md);
flex-wrap: nowrap;
+ padding: 0 var(--space-lg) var(--space-lg);
}
.documents-tab-bar {
@@ -847,25 +830,12 @@ The artifacts tab is a thumbnail-first responsive media gallery for agent-create
@media (max-width: 768px) {
- /* Documents View mobile */
- .documents-view-header {
- padding: var(--space-md);
- }
-
- .documents-view-title-row {
- flex-direction: column;
- align-items: flex-start;
- gap: var(--space-sm);
- }
-
- .documents-view-title {
- font-size: 16px;
- }
-
+ /* Documents View mobile: ViewHeader supplies its own responsive padding; only the controls row needs tightening here. */
.documents-controls-row {
flex-direction: column;
align-items: stretch;
gap: var(--space-sm);
+ padding: 0 var(--space-md) var(--space-md);
}
.documents-tab-bar {
diff --git a/packages/dashboard/app/components/DocumentsView.tsx b/packages/dashboard/app/components/DocumentsView.tsx
index 3de002d0ef..7bbca2de91 100644
--- a/packages/dashboard/app/components/DocumentsView.tsx
+++ b/packages/dashboard/app/components/DocumentsView.tsx
@@ -14,6 +14,7 @@ import { useSelectionComment } from "../hooks/useSelectionComment";
import { SelectionCommentPopover } from "./SelectionCommentPopover";
import { LoadingSpinner } from "./LoadingSpinner";
import { ArtifactMedia, getArtifactTypeLabel } from "./ArtifactMedia";
+import { ViewHeader } from "./ViewHeader";
const MOBILE_BREAKPOINT = 768;
@@ -538,17 +539,21 @@ export function DocumentsView({ projectId, addToast, onOpenDetail, onSendSelecti
return (
+ {/*
+ FNXC:Navigation 2026-06-22-01:10:
+ Documents/Artifacts adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the result count rides in the header actions while the tab bar, hidden-files toggle, and search stay in the controls row below.
+ FNXC:Navigation 2026-06-21-18:25: FN-6890 keeps the top-level title as Artifacts (renamed from Documents) without changing internal task-document tabs or artifact sub-tabs.
+ */}
-
-
-
- {/* FNXC:Navigation 2026-06-21-18:25: FN-6890 renames the top-level Documents view header to Artifacts without changing internal task-document tabs or artifact sub-tabs. */}
- {t("documents.title", "Artifacts")}
-
diff --git a/packages/dashboard/app/components/EvalsView.css b/packages/dashboard/app/components/EvalsView.css
index 7fdc2444f8..915dda430e 100644
--- a/packages/dashboard/app/components/EvalsView.css
+++ b/packages/dashboard/app/components/EvalsView.css
@@ -1,7 +1,17 @@
+/*
+FNXC:Navigation 2026-06-22-01:10:
+The shared .view-header sits at the top of the Evals view; the two-column results/detail grid moves into .evals-view__body so the header keeps the standard --space-lg inset while the body retains its side/bottom padding.
+*/
.evals-view {
+ display: flex;
+ flex-direction: column;
+}
+
+.evals-view__body {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
gap: var(--space-lg);
+ padding: 0 var(--space-lg) var(--space-lg);
}
.evals-list,
@@ -80,8 +90,9 @@
}
@media (max-width: 768px) {
- .evals-view {
+ .evals-view__body {
grid-template-columns: 1fr;
+ padding: 0 var(--space-md) var(--space-md);
}
.evals-toolbar {
diff --git a/packages/dashboard/app/components/EvalsView.tsx b/packages/dashboard/app/components/EvalsView.tsx
index d7759ce809..833cb2b3d9 100644
--- a/packages/dashboard/app/components/EvalsView.tsx
+++ b/packages/dashboard/app/components/EvalsView.tsx
@@ -1,10 +1,11 @@
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
-import { ExternalLink, RefreshCw, Settings } from "lucide-react";
+import { ExternalLink, RefreshCw, Settings, Target } from "lucide-react";
import { fetchSettings } from "../api";
import { useEvals } from "../hooks/useEvals";
import type { SectionId } from "./SettingsModal";
import { LoadingSpinner } from "./LoadingSpinner";
+import { ViewHeader } from "./ViewHeader";
import "./EvalsView.css";
interface EvalsViewProps {
@@ -51,7 +52,13 @@ export function EvalsView({ projectId, onOpenSettings, onOpenTaskDetail }: Evals
}
return (
+ /*
+ FNXC:Navigation 2026-06-22-01:10:
+ Evals adopts the shared ViewHeader (CC-modeled) so this main-content destination reads consistently with the others; the scored-results grid moves into a body wrapper beneath the header. The per-list Refresh control stays in the results toolbar.
+ */
+
+
)}
+
);
}
diff --git a/packages/dashboard/app/components/GoalsView.css b/packages/dashboard/app/components/GoalsView.css
index 6351b98a49..cf58a17e05 100644
--- a/packages/dashboard/app/components/GoalsView.css
+++ b/packages/dashboard/app/components/GoalsView.css
@@ -2,31 +2,28 @@
FNXC:GoalsViewStyling 2026-06-20-01:33:
FN-6789 mounts Goals as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix.
*/
+/*
+FNXC:Navigation 2026-06-22-01:10:
+The title row now comes from the shared .view-header (which supplies the --space-lg top/side padding). The root keeps scroll + sizing but drops its uniform padding; .goals-view__content carries the body's horizontal/bottom inset and the inter-block gap so cards stay aligned under the header.
+*/
.goals-view {
display: flex;
flex: 1 1 auto;
flex-direction: column;
- gap: var(--space-lg);
height: 100%;
min-height: 0;
min-width: 0;
width: 100%;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
- padding: var(--space-lg);
}
-.goals-header {
+.goals-view__content {
display: flex;
- justify-content: space-between;
- align-items: center;
- gap: var(--space-md);
-}
-
-.goals-title {
- margin: 0;
- color: var(--text);
- font-size: calc(var(--space-lg) + var(--space-xs));
+ flex-direction: column;
+ gap: var(--space-lg);
+ min-width: 0;
+ padding: 0 var(--space-lg) var(--space-lg);
}
.goals-count {
@@ -286,11 +283,7 @@ FN-6828 keeps Goals action controls and linked-mission panels at intrinsic heigh
}
@media (max-width: 768px) {
- .goals-header {
- flex-direction: column;
- align-items: stretch;
- }
-
+ /* .goals-header removed: the shared .view-header wraps its actions cluster on narrow widths. */
.goals-card {
flex-direction: column;
align-items: stretch;
diff --git a/packages/dashboard/app/components/GoalsView.tsx b/packages/dashboard/app/components/GoalsView.tsx
index c837d74758..0cfef9f73f 100644
--- a/packages/dashboard/app/components/GoalsView.tsx
+++ b/packages/dashboard/app/components/GoalsView.tsx
@@ -1,10 +1,11 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import type { Goal } from "@fusion/core";
-import { Link, Plus, Sparkles, X } from "lucide-react";
+import { Link, Plus, Sparkles, Target, X } from "lucide-react";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { draftGoalDescription, getRefineErrorMessage } from "../api";
+import { ViewHeader } from "./ViewHeader";
import "./GoalsView.css";
export interface GoalsViewProps {
@@ -430,18 +431,27 @@ export function GoalsView({ initialGoals, anchorGoalId, onNavigateToMission }: G
return (
-
-
-
{t("goals.title", "Goals")}
-
- {t("goals.activeCount", "{{count}} active goals", { count: activeCount })}
-
-
-
-
+ {/*
+ FNXC:Navigation 2026-06-22-01:10:
+ Goals adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the Add Goal action and the active-goal count both ride in the header actions cluster so existing behavior and the goals-active-count test hook are preserved.
+ */}
+
+
+ {t("goals.activeCount", "{{count}} active goals", { count: activeCount })}
+
+
+ >
+ )}
+ />
+ {/* FNXC:Navigation 2026-06-22-01:12: Inner content keeps its own horizontal padding via .goals-view__content so it aligns with the ViewHeader inset after the root drops its uniform padding. */}
+
{isAddFormOpen ? (
@@ -692,6 +702,7 @@ export function GoalsView({ initialGoals, anchorGoalId, onNavigateToMission }: G
))}
) : null}
+
);
}
diff --git a/packages/dashboard/app/components/MemoryView.css b/packages/dashboard/app/components/MemoryView.css
index bb80086647..1750f6b7ca 100644
--- a/packages/dashboard/app/components/MemoryView.css
+++ b/packages/dashboard/app/components/MemoryView.css
@@ -1,30 +1,20 @@
/* === MemoryView === */
+/*
+FNXC:Navigation 2026-06-22-01:10:
+The title row now comes from the shared .view-header (which supplies the --space-lg top/side padding). The root drops its uniform padding; the description, tab bar, and content area carry their own horizontal inset so they align under the header.
+*/
.memory-view {
display: flex;
flex-direction: column;
height: 100%;
- padding: var(--space-lg);
overflow: hidden;
}
-.memory-view-header {
- display: flex;
- flex-direction: row;
- justify-content: space-between;
- align-items: flex-start;
- margin-bottom: var(--space-lg);
-}
-
-.memory-view-header h2 {
- font-size: 18px;
- color: var(--text);
- margin: 0;
-}
-
.memory-view-description {
color: var(--text-muted);
font-size: 13px;
- margin: var(--space-xs) 0 0 0;
+ margin: 0;
+ padding: 0 var(--space-lg);
}
.memory-view-tabs {
@@ -32,7 +22,8 @@
flex-direction: row;
gap: var(--space-xs);
border-bottom: 1px solid var(--border);
- margin-bottom: var(--space-lg);
+ margin: var(--space-md) 0 var(--space-lg);
+ padding: 0 var(--space-lg);
}
.memory-view-tab {
@@ -67,6 +58,7 @@
min-height: 0;
display: flex;
flex-direction: column;
+ padding: 0 var(--space-lg) var(--space-lg);
}
.memory-working-tab,
@@ -441,13 +433,14 @@
/* Mobile responsive for memory view */
@media (max-width: 768px) {
- .memory-view {
- padding: var(--space-md);
+ /* ViewHeader supplies its own responsive padding; the body blocks tighten their horizontal inset here. */
+ .memory-view-description,
+ .memory-view-tabs {
+ padding-inline: var(--space-md);
}
- .memory-view-header {
- flex-direction: column;
- gap: var(--space-sm);
+ .memory-view-content {
+ padding: 0 var(--space-md) var(--space-md);
}
.memory-editor-container {
diff --git a/packages/dashboard/app/components/MemoryView.tsx b/packages/dashboard/app/components/MemoryView.tsx
index 7770ad5582..ad4e5a0dc2 100644
--- a/packages/dashboard/app/components/MemoryView.tsx
+++ b/packages/dashboard/app/components/MemoryView.tsx
@@ -1,10 +1,11 @@
import { useState, useMemo, useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
-import { Loader2 } from "lucide-react";
+import { Brain, Loader2 } from "lucide-react";
import "./MemoryView.css";
import "./SettingsModal.css";
import type { MemoryFileInfo, MemoryRetrievalTestResult } from "../api";
import { FileEditor } from "./FileEditor";
+import { ViewHeader } from "./ViewHeader";
import { useMemoryData } from "../hooks/useMemoryData";
interface MemoryViewProps {
@@ -349,15 +350,14 @@ export function MemoryView({ projectId, addToast, onSendSelectionToTask }: Memor
return (
- {/* Header */}
-
-
-
{t("memory.title", "Memory")}
-
- {t("memory.description", "Working memory, long-term insights, and engine status")}
-
-
-
+ {/*
+ FNXC:Navigation 2026-06-22-01:10:
+ Memory adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the prior description renders just below the header, and the Working/Insights/Engines tab bar stays beneath it.
+ */}
+
+
+ {t("memory.description", "Working memory, long-term insights, and engine status")}
+
{/* Tab bar */}
diff --git a/packages/dashboard/app/components/ResearchView.css b/packages/dashboard/app/components/ResearchView.css
index e005513ffd..b20bf32612 100644
--- a/packages/dashboard/app/components/ResearchView.css
+++ b/packages/dashboard/app/components/ResearchView.css
@@ -2,6 +2,10 @@
FNXC:ResearchViewStyling 2026-06-20-01:33:
FN-6789 mounts Research as a flex child of .project-content; grow, zero min-width, and use 100% width so the view fills the viewport instead of collapsing to intrinsic content width, mirroring the FN-6446 SecretsView fix.
*/
+/*
+FNXC:Navigation 2026-06-22-01:10:
+The title row now comes from the shared .view-header, which supplies the --space-lg top/side padding. The root drops its top/side inset (keeping a bottom inset) and the remaining body blocks (subtitle, layout, setup state) carry their own horizontal padding so they stay aligned with the header.
+*/
.research-view {
display: flex;
flex: 1 1 auto;
@@ -12,31 +16,19 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
min-width: 0;
width: 100%;
overflow: hidden;
- padding: var(--space-lg);
- padding-bottom: var(--space-lg);
-}
-
-.research-view__header {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: var(--space-md);
- min-width: 0;
-}
-
-.research-view__header-actions {
- flex-shrink: 0;
-}
-
-.research-view__title {
- margin: 0;
+ padding: 0 0 var(--space-lg);
}
.research-view__subtitle {
- margin: var(--space-xs) 0 0;
+ margin: 0;
+ padding-inline: var(--space-lg);
color: var(--text-muted);
}
+.research-view__state {
+ margin-inline: var(--space-lg);
+}
+
.research-view__layout {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
@@ -44,6 +36,7 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
min-height: 0;
flex: 1;
overflow: hidden;
+ padding-inline: var(--space-lg);
}
.research-view__sidebar,
@@ -256,10 +249,18 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
- padding: var(--space-md);
+ padding: 0;
padding-bottom: calc(var(--space-md) + var(--mobile-nav-height) + env(safe-area-inset-bottom, 0px) + var(--standalone-bottom-gap));
}
+ .research-view__subtitle {
+ padding-inline: var(--space-md);
+ }
+
+ .research-view__state {
+ margin-inline: var(--space-md);
+ }
+
.research-view__layout {
display: flex;
flex-direction: column;
@@ -268,6 +269,7 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
flex: initial;
min-height: auto;
overflow: visible;
+ padding-inline: var(--space-md);
}
.research-view__sidebar,
@@ -284,14 +286,6 @@ FN-6789 mounts Research as a flex child of .project-content; grow, zero min-widt
overflow: visible;
}
- .research-view__header {
- flex-direction: column;
- }
-
- .research-view__header-actions {
- align-self: flex-start;
- }
-
.research-view__stats {
grid-template-columns: minmax(0, 1fr);
}
diff --git a/packages/dashboard/app/components/ResearchView.tsx b/packages/dashboard/app/components/ResearchView.tsx
index 48aa0944af..5912307d8e 100644
--- a/packages/dashboard/app/components/ResearchView.tsx
+++ b/packages/dashboard/app/components/ResearchView.tsx
@@ -8,6 +8,7 @@ import { useResearch } from "../hooks/useResearch";
import type { ResearchProviderOption } from "../research-types";
import { ResearchTaskActionModal } from "./ResearchTaskActionModal";
import { LoadingSpinner } from "./LoadingSpinner";
+import { ViewHeader } from "./ViewHeader";
import type { SectionId } from "./SettingsModal";
import "./ResearchView.css";
import { recordResumeEvent } from "../utils/resumeInstrumentation";
@@ -252,17 +253,20 @@ export function ResearchView({ projectId, addToast, onOpenSettings, readinessVer
return (
-
-
-
{t("research.title", "Research")}
-
{t("research.subtitle", "Cited search and synthesis runs: gather sources, fetch content, and synthesize findings.")}
-
-
+ {/*
+ FNXC:Navigation 2026-06-22-01:10:
+ Research adopts the shared ViewHeader (CC-modeled) for a consistent main-content title row; the Refresh action moves into the header actions cluster and the prior subtitle renders just below the header so the descriptive copy is preserved.
+ */}
+ void refresh()}>
{t("actions.refresh", "Refresh")}
-
-
+ )}
+ />
+
{t("research.subtitle", "Cited search and synthesis runs: gather sources, fetch content, and synthesize findings.")}