feat(dashboard): finish ViewHeader rollout + normalize content padding
- Migrate Insights, Dev Server, Todos, Pull Requests to the shared ViewHeader. - Fix doubled top padding (ViewHeader already pads top) across Documents/Agents/Mailbox/Missions content areas. - Nudge embedded-view headers to the shared spec (CommandCenter icon 20; WorkflowNodeEditor icon+1.125rem title+space-lg; Settings embedded icon). ChatView intentionally headerless. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 (
|
||||
<div className="dev-server-view" data-testid="dev-server-view">
|
||||
<section className="dev-server-header" aria-label={t("devserver.controlsHeaderLabel", "Dev server controls header")}>
|
||||
<div className="dev-server-header-title">
|
||||
<Monitor size={16} />
|
||||
<h2>{t("devserver.title", "Dev Server")}</h2>
|
||||
<span
|
||||
className={`dev-server-status-badge ${statusBadge.className}`}
|
||||
data-testid="dev-server-status-badge"
|
||||
>
|
||||
{statusBadge.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="dev-server-header-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={handleStart}
|
||||
disabled={startDisabled}
|
||||
data-testid="dev-server-start-button"
|
||||
>
|
||||
<Play size={14} />
|
||||
<span>{actionInFlight === "start" ? t("devserver.starting", "Starting...") : t("devserver.start", "Start")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger btn-sm"
|
||||
onClick={handleStop}
|
||||
disabled={stopDisabled}
|
||||
data-testid="dev-server-stop-button"
|
||||
>
|
||||
<Square size={14} />
|
||||
<span>{actionInFlight === "stop" ? t("devserver.stopping", "Stopping...") : t("devserver.stop", "Stop")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={handleRestart}
|
||||
disabled={restartDisabled}
|
||||
data-testid="dev-server-restart-button"
|
||||
>
|
||||
<RotateCw size={14} />
|
||||
<span>{actionInFlight === "restart" ? t("devserver.restarting", "Restarting...") : t("devserver.restart", "Restart")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
{/*
|
||||
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.
|
||||
*/}
|
||||
<ViewHeader
|
||||
icon={Monitor}
|
||||
title={t("devserver.title", "Dev Server")}
|
||||
actions={(
|
||||
<>
|
||||
<span className="dev-server-header-title">
|
||||
<span
|
||||
className={`dev-server-status-badge ${statusBadge.className}`}
|
||||
data-testid="dev-server-status-badge"
|
||||
>
|
||||
{statusBadge.label}
|
||||
</span>
|
||||
</span>
|
||||
<div className="dev-server-header-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary btn-sm"
|
||||
onClick={handleStart}
|
||||
disabled={startDisabled}
|
||||
data-testid="dev-server-start-button"
|
||||
>
|
||||
<Play size={14} />
|
||||
<span>{actionInFlight === "start" ? t("devserver.starting", "Starting...") : t("devserver.start", "Start")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-danger btn-sm"
|
||||
onClick={handleStop}
|
||||
disabled={stopDisabled}
|
||||
data-testid="dev-server-stop-button"
|
||||
>
|
||||
<Square size={14} />
|
||||
<span>{actionInFlight === "stop" ? t("devserver.stopping", "Stopping...") : t("devserver.stop", "Stop")}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm"
|
||||
onClick={handleRestart}
|
||||
disabled={restartDisabled}
|
||||
data-testid="dev-server-restart-button"
|
||||
>
|
||||
<RotateCw size={14} />
|
||||
<span>{actionInFlight === "restart" ? t("devserver.restarting", "Restarting...") : t("devserver.restart", "Restart")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
<section className="dev-server-panel dev-server-config" aria-label={t("devserver.configurationLabel", "Dev server configuration")}>
|
||||
<div className="dev-server-section-header">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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%;
|
||||
|
||||
@@ -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 (
|
||||
<div className="insights-view" data-testid="insights-view">
|
||||
<div className="insights-view-header">
|
||||
<div className="insights-view-title">
|
||||
<h2>
|
||||
<Sparkles size={20} />
|
||||
{t("insights.title", "Insights")}
|
||||
</h2>
|
||||
<span className="insights-view-count">{totalCount} {t("common.total", "total")}</span>
|
||||
</div>
|
||||
|
||||
<div className="insights-view-actions">
|
||||
{/*
|
||||
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.
|
||||
*/}
|
||||
<ViewHeader
|
||||
icon={Sparkles}
|
||||
title={t("insights.title", "Insights")}
|
||||
actions={(
|
||||
<>
|
||||
<span className="insights-view-count">{totalCount} {t("common.total", "total")}</span>
|
||||
{backlogHealthCount > 0 && (
|
||||
<button
|
||||
className={`btn btn-sm insights-backlog-health-toggle${backlogHealthOnly ? " btn-icon--active" : ""}`}
|
||||
@@ -564,8 +565,9 @@ export function InsightsView({ projectId, addToast, onClose, onCreateTask, model
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
{showModelConfig && (
|
||||
<div className="insights-model-config" data-testid="model-config">
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = <ViewHeader icon={GitPullRequest} title={t("pr.view.title", "Pull Requests")} />;
|
||||
|
||||
// ── creating ───────────────────────────────────────────────────────────────
|
||||
if (state === "creating") {
|
||||
return (
|
||||
<div className="pr-view" data-testid="pr-view" data-state="creating">
|
||||
{viewHeader}
|
||||
<PrIdentityHeader detail={detail} />
|
||||
<div className="pr-placeholder" data-testid="pr-creating">
|
||||
<Clock size={16} /> {t("pr.view.creating", "Creating PR…")}
|
||||
@@ -191,6 +199,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
if (state === "failed") {
|
||||
return (
|
||||
<div className="pr-view" data-testid="pr-view" data-state="failed">
|
||||
{viewHeader}
|
||||
<PrIdentityHeader detail={detail} />
|
||||
<div className="pr-error-reason" data-testid="pr-failed">
|
||||
<AlertTriangle size={16} className="pr-icon-failure" />
|
||||
@@ -216,6 +225,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
if (detail.unverified) {
|
||||
return (
|
||||
<div className="pr-view" data-testid="pr-view" data-state="unverified">
|
||||
{viewHeader}
|
||||
<PrIdentityHeader detail={detail} />
|
||||
<div className="pr-notice pr-notice--unverified" data-testid="pr-unverified">
|
||||
<Clock size={16} /> {t("pr.view.verifyingGithub", "Verifying with GitHub…")}
|
||||
@@ -240,6 +250,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
|
||||
return (
|
||||
<div className="pr-view" data-testid="pr-view" data-state={state}>
|
||||
{viewHeader}
|
||||
<PrIdentityHeader detail={detail} />
|
||||
|
||||
{/* responding banner */}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
<div className={isEmbedded ? "modal-header modal-header--embedded" : "modal-header"}>
|
||||
{/* 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. */}
|
||||
<div className="settings-modal-heading">
|
||||
<h3>{t("settings.title", "Settings")}</h3>
|
||||
<h3>
|
||||
{isEmbedded && <SettingsIcon size={20} aria-hidden="true" />}
|
||||
<span>{t("settings.title", "Settings")}</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div className="settings-header-actions">
|
||||
<a
|
||||
|
||||
@@ -13,7 +13,11 @@ FN-6829 mounts Todos as a flex child of .project-content like GoalsView; grow, z
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
padding: var(--space-lg);
|
||||
/*
|
||||
FNXC:TodosStyling 2026-06-22-01:00:
|
||||
Header migrated to the shared ViewHeader, which supplies the --space-lg top/side padding. The view drops its own top padding so the gap under the header is only ViewHeader's --space-md bottom; side and bottom padding remain.
|
||||
*/
|
||||
padding: 0 var(--space-lg) var(--space-lg);
|
||||
/*
|
||||
FNXC:TodosStyling 2026-06-22-00:00:
|
||||
TodoView renders both in the wide main area and inside the narrow right dock (no width prop). Make it a query container so the layout switch is driven by the actual rendered width, not a viewport media query or a prop. Below the container breakpoint the two-panel split collapses into a single-panel navigation stack (see `@container todo-view (max-width: 520px)`).
|
||||
@@ -30,26 +34,11 @@ The narrow-stack Back button is hidden by default (wide two-panel layout shows b
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-view-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.todo-view-title-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.todo-view-title-group h2 {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: calc(var(--space-lg) + var(--space-xs));
|
||||
}
|
||||
|
||||
.todo-view-title-group p {
|
||||
/*
|
||||
FNXC:TodosStyling 2026-06-22-01:00:
|
||||
The descriptive subtitle renders inside ViewHeader's actions slot; mute it so it reads as secondary text alongside the title.
|
||||
*/
|
||||
.todo-view-subtitle {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
@@ -504,7 +493,7 @@ NARROW container (right dock): collapse the side-by-side split into a single-pan
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.todo-view {
|
||||
padding: var(--space-md);
|
||||
padding: 0 var(--space-md) var(--space-md);
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,12 @@ import {
|
||||
ChevronLeft,
|
||||
Loader2,
|
||||
ListChecks,
|
||||
CheckSquare,
|
||||
Bot,
|
||||
PlusCircle,
|
||||
Lightbulb,
|
||||
} from "lucide-react";
|
||||
import { ViewHeader } from "./ViewHeader";
|
||||
import { getErrorMessage, type Task, type TaskCreateInput, type TodoItem, type TodoList } from "@fusion/core";
|
||||
import { createTask, fetchAgents } from "../api";
|
||||
import type { Agent } from "../api";
|
||||
@@ -316,16 +318,18 @@ export function TodoView({
|
||||
}
|
||||
}, [projectId, addToast, agents, onTaskCreated, t]);
|
||||
|
||||
/*
|
||||
FNXC:Todos 2026-06-22-01:00:
|
||||
Migrated to the shared ViewHeader (CheckSquare icon, matching the left-sidebar nav) so Todos reads consistently with the other main-content views. The descriptive subtitle moves into the actions slot so it stays visible while the icon + 1.125rem title come from ViewHeader. The header sits above the two-pane/stack layout (flex-shrink:0); the layout owns its own scroll.
|
||||
*/
|
||||
const header = (
|
||||
<header className="todo-view-header">
|
||||
<div className="todo-view-title-group">
|
||||
<ListChecks aria-hidden="true" />
|
||||
<div>
|
||||
<h2>{t("todo.todos", "Todos")}</h2>
|
||||
<p>{t("todo.manageDescription", "Manage reusable todo lists for your project.")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ViewHeader
|
||||
icon={CheckSquare}
|
||||
title={t("todo.todos", "Todos")}
|
||||
actions={(
|
||||
<p className="todo-view-subtitle">{t("todo.manageDescription", "Manage reusable todo lists for your project.")}</p>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
|
||||
@@ -47,17 +47,22 @@ embedded view.
|
||||
--wf-editor-touch-target: calc(var(--space-xl) + var(--space-lg) + var(--space-xs));
|
||||
}
|
||||
|
||||
/* FNXC:WorkflowEditorEmbedding 2026-06-22-01:00: Align the embedded workflows header to the shared ViewHeader metric — --space-lg padding, an icon + 1.125rem title — for cross-view consistency. */
|
||||
.wf-editor-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-md);
|
||||
padding: var(--space-lg);
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.wf-editor-header h2 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
|
||||
@@ -2479,7 +2479,11 @@ function InnerEditor({
|
||||
}}
|
||||
>
|
||||
<header className="wf-editor-header">
|
||||
<h2>{t("workflows.title", "Workflows")}</h2>
|
||||
{/* FNXC:WorkflowEditorEmbedding 2026-06-22-01:00: Title row aligned to the shared ViewHeader/Command Center metric — a Workflow icon (size 20) + 1.125rem title — so the embedded workflows view reads consistently with other main-content destinations. */}
|
||||
<h2>
|
||||
<Workflow size={20} aria-hidden="true" />
|
||||
<span>{t("workflows.title", "Workflows")}</span>
|
||||
</h2>
|
||||
{/* FNXC:WorkflowEditorEmbedding 2026-06-22-00:00: embedded views keep a
|
||||
Command Center-style header title but drop the modal X close button. */}
|
||||
{!isEmbedded ? (
|
||||
|
||||
@@ -32,6 +32,7 @@ vi.mock("lucide-react", () => ({
|
||||
ChevronLeft: () => <span data-testid="icon-chevron-left" />,
|
||||
Loader2: () => <span data-testid="icon-loader" />,
|
||||
ListChecks: () => <span data-testid="icon-list-checks" />,
|
||||
CheckSquare: () => <span data-testid="icon-check-square" />,
|
||||
Bot: () => <span data-testid="icon-bot" />,
|
||||
PlusCircle: () => <span data-testid="icon-plus-circle" />,
|
||||
Lightbulb: () => <span data-testid="icon-lightbulb" />,
|
||||
|
||||
@@ -562,8 +562,9 @@ export function CommandCenter({
|
||||
return (
|
||||
<section className="command-center" data-testid="command-center">
|
||||
<header className="cc-header">
|
||||
{/* FNXC:CommandCenter 2026-06-22-01:00: Icon size aligned to 20 to match the shared ViewHeader (cc-header is the model for ViewHeader; title is already 1.125rem with --space-lg padding). */}
|
||||
<h2 className="cc-title">
|
||||
<Gauge size={18} />
|
||||
<Gauge size={20} />
|
||||
{t("commandCenter.heading", "Command Center")}
|
||||
</h2>
|
||||
<DateRangePicker value={range} onChange={setRange} />
|
||||
|
||||
Reference in New Issue
Block a user