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")} -

- - {t("documents.resultCount", "{{count}} result{{plural}}", { count: activeCount, plural: activeCount !== 1 ? "s" : "" })} - -
+ + {t("documents.resultCount", "{{count}} result{{plural}}", { count: activeCount, plural: activeCount !== 1 ? "s" : "" })} + + )} + />
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.")}

{setupState ? (
diff --git a/packages/dashboard/app/components/__tests__/GoalsView.test.tsx b/packages/dashboard/app/components/__tests__/GoalsView.test.tsx index 2dbf275200..4fd6b7835c 100644 --- a/packages/dashboard/app/components/__tests__/GoalsView.test.tsx +++ b/packages/dashboard/app/components/__tests__/GoalsView.test.tsx @@ -14,6 +14,8 @@ vi.mock("lucide-react", () => ({ Link: () => , Plus: () => , Sparkles: () => , + // Target backs the shared ViewHeader icon for the Goals view header (FNXC:Navigation 2026-06-22). + Target: () => , X: () => , })); diff --git a/packages/dashboard/app/components/__tests__/MemoryView.test.tsx b/packages/dashboard/app/components/__tests__/MemoryView.test.tsx index c67b566a8d..6be03de03b 100644 --- a/packages/dashboard/app/components/__tests__/MemoryView.test.tsx +++ b/packages/dashboard/app/components/__tests__/MemoryView.test.tsx @@ -23,6 +23,8 @@ vi.mock("../FileEditor", () => ({ vi.mock("lucide-react", () => ({ Loader2: () => , + // Brain backs the shared ViewHeader icon for the Memory view header (FNXC:Navigation 2026-06-22). + Brain: () => , })); function createMemoryData(overrides: Record = {}) {