diff --git a/packages/dashboard/app/components/AgentsView.css b/packages/dashboard/app/components/AgentsView.css index eb0cea5a86..129ecc2b6a 100644 --- a/packages/dashboard/app/components/AgentsView.css +++ b/packages/dashboard/app/components/AgentsView.css @@ -278,11 +278,12 @@ The base grid keeps a token-sized handle column as the no-JS fallback, while the background: var(--surface); } +/* FNXC:AgentsView 2026-06-22-01:00: ViewHeader supplies the top padding, so the scrollable body drops its top inset to avoid doubling the gap under the header (keeps horizontal + bottom padding). */ .agents-view-content { flex: 1; min-height: 0; overflow-y: auto; - padding: calc(var(--space-lg) + var(--space-xs)); + padding: 0 calc(var(--space-lg) + var(--space-xs)) calc(var(--space-lg) + var(--space-xs)); } diff --git a/packages/dashboard/app/components/DevServerView.css b/packages/dashboard/app/components/DevServerView.css index 2bdca7348a..c6bfe4d6b7 100644 --- a/packages/dashboard/app/components/DevServerView.css +++ b/packages/dashboard/app/components/DevServerView.css @@ -1,10 +1,14 @@ /* === DevServerView === */ +/* +FNXC:DevServer 2026-06-22-01:00: +Header migrated to the shared ViewHeader (.view-header), which supplies the --space-lg top/side and --space-md bottom padding. The view no longer adds its own top padding (was causing a doubled gap under the header); only the side and bottom padding remain. +*/ .dev-server-view { display: flex; flex-direction: column; gap: var(--space-md); - padding: var(--space-lg); + padding: 0 var(--space-lg) var(--space-lg); min-height: 0; height: 100%; overflow-y: auto; @@ -13,29 +17,15 @@ } /* -FNXC:DevServer 2026-06-22-00:00: -The Dev Server view header must read like other embedded views (Command Center cc-header): a plain title row with actions, not a bordered card. Drop the card border/background/padding so the heading sits flush with the view padding; the title font already matches the shared 1.125rem embedded-title size. +FNXC:DevServer 2026-06-22-01:00: +.dev-server-header-title now wraps just the status badge inside ViewHeader's actions slot; the mobile flex-wrap rule keeps it from overflowing on narrow widths. */ -.dev-server-header { - display: flex; - justify-content: space-between; - align-items: center; - gap: var(--space-md); -} - .dev-server-header-title { display: flex; align-items: center; gap: var(--space-sm); } -.dev-server-header-title h2 { - margin: 0; - font-size: 1.125rem; - font-weight: 600; - color: var(--text); -} - .dev-server-header-actions { display: flex; align-items: center; @@ -542,7 +532,7 @@ The Dev Server view header must read like other embedded views (Command Center c grid-template-rows: auto auto 1fr; } - .dev-server-header { + .dev-server-view > .view-header { grid-column: 1 / -1; } @@ -572,12 +562,7 @@ The Dev Server view header must read like other embedded views (Command Center c .dev-server-view { display: flex; flex-direction: column; - padding: var(--space-md); - } - - .dev-server-header { - flex-direction: column; - align-items: flex-start; + padding: 0 var(--space-md) var(--space-md); } .dev-server-header-title { diff --git a/packages/dashboard/app/components/DevServerView.tsx b/packages/dashboard/app/components/DevServerView.tsx index 8c88886db3..ecca3d4d06 100644 --- a/packages/dashboard/app/components/DevServerView.tsx +++ b/packages/dashboard/app/components/DevServerView.tsx @@ -11,6 +11,7 @@ import type { ToastType } from "../hooks/useToast"; import { DevServerLogViewer } from "./DevServerLogViewer"; import { PreviewIframe } from "./PreviewIframe"; import { recordResumeEvent } from "../utils/resumeInstrumentation"; +import { ViewHeader } from "./ViewHeader"; interface DevServerViewProps { addToast: (msg: string, type?: ToastType) => void; @@ -365,50 +366,58 @@ export function DevServerView({ addToast, projectId }: DevServerViewProps) { return (
-
-
- -

{t("devserver.title", "Dev Server")}

- - {statusBadge.label} - -
-
- - - -
-
+ {/* + FNXC:DevServer 2026-06-22-01:00: + Migrated to the shared ViewHeader for cross-view consistency. The status badge sits next to the title inside the actions slot (wrapped in .dev-server-header-title so the existing mobile flex-wrap rule still applies), and the Start/Stop/Restart controls follow in .dev-server-header-actions. ViewHeader supplies the standard view padding; the view body must not repeat the top padding. + */} + + + + {statusBadge.label} + + +
+ + + +
+ + )} + />
diff --git a/packages/dashboard/app/components/DocumentsView.css b/packages/dashboard/app/components/DocumentsView.css index 2a42034ff4..36979534a5 100644 --- a/packages/dashboard/app/components/DocumentsView.css +++ b/packages/dashboard/app/components/DocumentsView.css @@ -149,11 +149,12 @@ The header row now comes from the shared .view-header (which supplies the --spac box-shadow: var(--focus-ring-strong); } +/* FNXC:DocumentsView 2026-06-22-01:00: ViewHeader supplies the top padding, so the scrollable content body drops its top inset to avoid doubling the gap under the header (keeps horizontal + bottom padding). */ .documents-view-content { flex: 1; min-height: 0; overflow: auto; - padding: var(--space-lg); + padding: 0 var(--space-lg) var(--space-lg); } .documents-view-loading, diff --git a/packages/dashboard/app/components/InsightsView.css b/packages/dashboard/app/components/InsightsView.css index 5b2d4d8014..cb52777d28 100644 --- a/packages/dashboard/app/components/InsightsView.css +++ b/packages/dashboard/app/components/InsightsView.css @@ -8,48 +8,15 @@ } /* -FNXC:Insights 2026-06-22-00:00: -The header title and action buttons must never overlap: allow the row to wrap so the actions drop to a new line when there is not enough horizontal room. The gap keeps spacing between the wrapped rows. +FNXC:Insights 2026-06-22-01:00: +Header migrated to the shared ViewHeader component (.view-header). The old .insights-view-header / .insights-view-title / .insights-view-actions rules were removed; the count badge and action toggles still render inside ViewHeader's actions slot, so their styling rules are retained below. */ -.insights-view-header { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: var(--space-sm); - padding: var(--space-lg); - border-bottom: 1px solid var(--border); - background: var(--surface); - flex-shrink: 0; -} - -.insights-view-title { - display: flex; - align-items: center; - gap: var(--space-sm); -} - -.insights-view-title h2 { - margin: 0; - font-size: 1.125rem; - font-weight: 600; - display: flex; - align-items: center; - gap: var(--space-sm); -} - .insights-view-count { font-size: 0.8125rem; color: var(--text-muted); font-weight: normal; } -.insights-view-actions { - display: flex; - align-items: center; - gap: var(--space-sm); -} - /* Model configuration row — collapsible, below the action bar */ .insights-model-config { display: flex; @@ -493,31 +460,6 @@ The header title and action buttons must never overlap: allow the row to wrap so /* Mobile responsive: stack panes vertically; sidebar becomes a horizontal scroller */ @media (max-width: 768px) { - .insights-view-header { - flex-wrap: nowrap; - gap: var(--space-sm); - padding: var(--space-md); - } - - .insights-view-title { - min-width: 0; - flex: 1 1 auto; - } - - .insights-view-title h2 { - font-size: 1rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .insights-view-actions { - flex-shrink: 0; - justify-content: flex-end; - flex-wrap: nowrap; - gap: var(--space-xs); - } - .insights-body { flex-direction: column; } @@ -591,6 +533,7 @@ The header title and action buttons must never overlap: allow the row to wrap so height: calc(var(--space-2xl) + var(--space-sm)); } + .view-header__actions .insights-view-actions-btn, .insights-view-actions .btn { min-height: calc(var(--space-2xl) + var(--space-xs)); } @@ -633,19 +576,6 @@ Insights tablets at 769px–1024px were inheriting the desktop fixed category si overflow: hidden; } - .insights-view-header { - min-inline-size: 0; - } - - .insights-view-title { - flex: 1 1 auto; - min-inline-size: 0; - } - - .insights-view-actions { - flex-shrink: 0; - } - .insights-body { flex-direction: column; inline-size: 100%; diff --git a/packages/dashboard/app/components/InsightsView.tsx b/packages/dashboard/app/components/InsightsView.tsx index b50b27a904..cd5fd86bdf 100644 --- a/packages/dashboard/app/components/InsightsView.tsx +++ b/packages/dashboard/app/components/InsightsView.tsx @@ -27,6 +27,7 @@ import { Activity, } from "lucide-react"; import { CustomModelDropdown } from "./CustomModelDropdown"; +import { ViewHeader } from "./ViewHeader"; import { fetchModels, updateGlobalSettings, type ModelInfo } from "../api"; import { useInsights, type InsightSection } from "../hooks/useInsights"; import { BACKLOG_HEALTH_TITLE_PREFIXES, isBacklogHealthInsight } from "./backlog-health-filter"; @@ -480,16 +481,16 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model return (
-
-
-

- - {t("insights.title", "Insights")} -

- {totalCount} {t("common.total", "total")} -
- -
+ {/* + FNXC:Insights 2026-06-22-01:00: + Migrated to the shared ViewHeader for consistency with other main-content views. The insight count and action buttons live in the actions slot; ViewHeader already provides the --space-lg side/top padding and --space-md bottom gap, so the view body must not repeat the top padding. + */} + + {totalCount} {t("common.total", "total")} {backlogHealthCount > 0 && ( -
-
+ + )} + /> {showModelConfig && (
diff --git a/packages/dashboard/app/components/MailboxModal.css b/packages/dashboard/app/components/MailboxModal.css index 02b220a7e1..63406b973f 100644 --- a/packages/dashboard/app/components/MailboxModal.css +++ b/packages/dashboard/app/components/MailboxModal.css @@ -652,12 +652,13 @@ padding-top: var(--space-sm); } +/* FNXC:MailboxView 2026-06-22-01:00: ViewHeader supplies the top padding, so the standalone-view scroll body drops its top inset to avoid doubling the gap under the header (keeps horizontal + bottom padding). */ .mailbox-view .mailbox-content { flex: 1; min-height: 0; overflow-x: hidden; overflow-y: auto; - padding: var(--space-xl); + padding: 0 var(--space-xl) var(--space-xl); max-height: none; } diff --git a/packages/dashboard/app/components/MissionManager.css b/packages/dashboard/app/components/MissionManager.css index 586a8b111b..80d256f266 100644 --- a/packages/dashboard/app/components/MissionManager.css +++ b/packages/dashboard/app/components/MissionManager.css @@ -139,13 +139,14 @@ Title metric matches the shared ViewHeader (1.125rem) so the Missions header rea } /* ── Body ── */ +/* FNXC:MissionManager 2026-06-22-01:00: The aligned .mission-manager__header supplies the top padding, so the scroll body drops its top inset to avoid doubling the gap under the header (keeps horizontal + bottom padding). */ .mission-manager__body { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; overscroll-behavior: contain; - padding: var(--space-lg); + padding: 0 var(--space-lg) var(--space-lg); -webkit-overflow-scrolling: touch; } diff --git a/packages/dashboard/app/components/PullRequestView.css b/packages/dashboard/app/components/PullRequestView.css index ef23d55c19..6fd02f252c 100644 --- a/packages/dashboard/app/components/PullRequestView.css +++ b/packages/dashboard/app/components/PullRequestView.css @@ -1,3 +1,7 @@ +/* +FNXC:PullRequests 2026-06-22-01:00: +The view now renders the shared ViewHeader at the top, which supplies the --space-lg top/side padding. The view drops its own top padding so the gap under the header is just ViewHeader's --space-md bottom; side and bottom padding remain. +*/ .pr-view { display: flex; flex-direction: column; @@ -6,7 +10,7 @@ min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch; - padding: var(--space-lg); + padding: 0 var(--space-lg) var(--space-lg); color: var(--text); } diff --git a/packages/dashboard/app/components/PullRequestView.tsx b/packages/dashboard/app/components/PullRequestView.tsx index 4306c04434..6fe624b1d5 100644 --- a/packages/dashboard/app/components/PullRequestView.tsx +++ b/packages/dashboard/app/components/PullRequestView.tsx @@ -13,6 +13,7 @@ import { MessageSquare, } from "lucide-react"; import { api } from "../api"; +import { ViewHeader } from "./ViewHeader"; import "./PullRequestView.css"; // Mirrors the route's serialized entity (register-pull-requests-routes.ts). @@ -175,10 +176,17 @@ export function PullRequestView(props: PullRequestViewProps) { const { state, summary } = detail; + /* + FNXC:PullRequests 2026-06-22-01:00: + Added the shared ViewHeader (GitPullRequest icon, matching the left-sidebar nav) at the top of every populated PR state so the view reads consistently with other main-content views. The PR-specific identity row (repo/number/branch/state) stays below it. ViewHeader supplies the standard --space-lg top/side padding; the view body must not repeat the top padding. + */ + const viewHeader = ; + // ── creating ─────────────────────────────────────────────────────────────── if (state === "creating") { return (
+ {viewHeader}
{t("pr.view.creating", "Creating PR…")} @@ -191,6 +199,7 @@ export function PullRequestView(props: PullRequestViewProps) { if (state === "failed") { return (
+ {viewHeader}
@@ -216,6 +225,7 @@ export function PullRequestView(props: PullRequestViewProps) { if (detail.unverified) { return (
+ {viewHeader}
{t("pr.view.verifyingGithub", "Verifying with GitHub…")} @@ -240,6 +250,7 @@ export function PullRequestView(props: PullRequestViewProps) { return (
+ {viewHeader} {/* responding banner */} diff --git a/packages/dashboard/app/components/SettingsModal.css b/packages/dashboard/app/components/SettingsModal.css index a8af66dbca..a5d8bd86cd 100644 --- a/packages/dashboard/app/components/SettingsModal.css +++ b/packages/dashboard/app/components/SettingsModal.css @@ -145,6 +145,9 @@ The embedded title reads like other embedded-view titles (Planning modal-header- } .settings-modal--embedded .modal-header--embedded .settings-modal-heading h3 { + display: flex; + align-items: center; + gap: var(--space-sm); font-size: 1.125rem; letter-spacing: normal; } diff --git a/packages/dashboard/app/components/SettingsModal.tsx b/packages/dashboard/app/components/SettingsModal.tsx index bbdc5bb45a..c2aac913f5 100644 --- a/packages/dashboard/app/components/SettingsModal.tsx +++ b/packages/dashboard/app/components/SettingsModal.tsx @@ -1,5 +1,5 @@ import { useState, useEffect, useCallback, useRef, type CSSProperties, type MouseEvent } from "react"; -import { Globe, Folder, RefreshCw, Star, HelpCircle } from "lucide-react"; +import { Globe, Folder, RefreshCw, Star, HelpCircle, Settings as SettingsIcon } from "lucide-react"; import { getErrorMessage, normalizeMergeIntegrationWorktreeMode, @@ -2920,8 +2920,12 @@ export function SettingsModal({ style={isEmbedded ? undefined : keyboardStyle} >
+ {/* FNXC:Settings 2026-06-22-01:00: Embedded title gains a Settings icon (size 20, matching the sidebar nav and shared ViewHeader) so the embedded settings panel reads consistently with other main-content destinations; title is already 1.125rem. */}
-

{t("settings.title", "Settings")}

+

+ {isEmbedded &&