diff --git a/.changeset/dashboard-view-chrome-alignment.md b/.changeset/dashboard-view-chrome-alignment.md
new file mode 100644
index 0000000000..a1adc5efe2
--- /dev/null
+++ b/.changeset/dashboard-view-chrome-alignment.md
@@ -0,0 +1,3 @@
+"@runfusion/fusion": patch
+
+Align dashboard view chrome and inner-pane spacing across Chat, Mailbox, Workflows, Artifacts-adjacent controls, Goals, and Compound Engineering.
diff --git a/packages/dashboard/app/components/ChatView.css b/packages/dashboard/app/components/ChatView.css
index 3111c9670d..72d0fde4c7 100644
--- a/packages/dashboard/app/components/ChatView.css
+++ b/packages/dashboard/app/components/ChatView.css
@@ -2,12 +2,20 @@
.chat-view {
display: flex;
- flex: 1;
+ flex-direction: column;
+ flex: 1 1 auto;
width: 100%;
height: 100%;
- width: 100%;
+ min-width: 0;
+ min-height: 0;
+ overflow: hidden;
+}
+
+.chat-view__body {
+ display: flex;
flex: 1 1 auto;
min-width: 0;
+ min-height: 0;
overflow: hidden;
}
@@ -645,7 +653,7 @@ Mobile chat session switching needs a dedicated rename tap target beside each se
overflow-wrap: anywhere;
}
-.chat-thread-header-new-chat {
+.chat-view-header-new-chat {
flex-shrink: 0;
}
@@ -1775,7 +1783,7 @@ Mobile chat session switching needs a dedicated rename tap target beside each se
thread takes the full viewport. The thread already renders a back
button (ChevronLeft) on mobile to flip back to the session list. */
@media (max-width: 768px) {
- .chat-view {
+ .chat-view__body {
flex-direction: column;
}
@@ -1889,10 +1897,6 @@ Mobile chat session switching needs a dedicated rename tap target beside each se
flex-shrink: 0;
}
- .chat-thread-header-new-chat {
- display: none;
- }
-
.chat-sidebar-scope-btn {
min-height: calc(var(--space-lg) * 2.25);
}
diff --git a/packages/dashboard/app/components/ChatView.tsx b/packages/dashboard/app/components/ChatView.tsx
index 0aeaabfe27..a76237eed0 100644
--- a/packages/dashboard/app/components/ChatView.tsx
+++ b/packages/dashboard/app/components/ChatView.tsx
@@ -53,6 +53,7 @@ import { recordResumeEvent } from "../utils/resumeInstrumentation";
import { parseQuestionToolCall } from "../utils/parseQuestionToolCall";
import { useTranslation } from "react-i18next";
import type { TFunction } from "i18next";
+import { ViewHeader } from "./ViewHeader";
export interface ChatViewProps {
projectId?: string;
@@ -3183,10 +3184,27 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
return (
/*
- FNXC:Navigation 2026-06-22-01:10:
- Chat deliberately does NOT adopt the shared ViewHeader. Unlike the other main-content views, .chat-view has no view-level title row to swap: its root is a height-sensitive two-pane flex-row (sidebar | thread) and each pane already owns its own header (chat-thread-header / chat-sidebar). The thread height is driven by flex:1 + the mobile-keyboard drift compensation applied directly to .chat-thread, so prepending a full-width column header would change the flex/height contract that those keyboard/scroll/resize paths depend on. Stacking a redundant "Chat" title above the existing thread header would also be visually duplicative. Consistency is therefore intentionally scoped to the views that have a single title row.
+ FNXC:Chat 2026-06-22-12:55:
+ Chat uses the shared ViewHeader so its page chrome matches the other main-content views. The height-sensitive two-pane chat layout remains isolated in .chat-view__body beneath that header, preserving sidebar resize, thread scrolling, and mobile keyboard compensation while moving the desktop New Chat action into the canonical header actions cluster.
*/
+
setShowNewDialog(true)}
+ data-testid="chat-new-btn"
+ >
+
+ {t("chat.newChat", "New Chat")}
+
+ ) : null
+ }
+ />
+
{/* Sidebar */}
) : null
) : (
-
-
setShowNewDialog(true)}
- data-testid="chat-new-btn"
- >
-
- {t("chat.newChat", "New Chat")}
-
-
+ isMobile ? (
+
+
setShowNewDialog(true)}
+ data-testid="chat-new-btn"
+ >
+
+ {t("chat.newChat", "New Chat")}
+
+
+ ) : null
)}
@@ -3890,17 +3910,6 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
{showAllAsPlain ?
:
}
)}
- {!isMobile && (
-
setShowNewDialog(true)}
- data-testid="chat-thread-new-chat-btn"
- >
-
- {t("chat.newChat", "New Chat")}
-
- )}
-
)}
@@ -3953,6 +3962,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
}}
/>
)}
+
{/* New Chat Dialog (rendered at root level) */}
{showNewDialog && (
diff --git a/packages/dashboard/app/components/GitHubImportModal.css b/packages/dashboard/app/components/GitHubImportModal.css
index deda6a1b47..6e95e887af 100644
--- a/packages/dashboard/app/components/GitHubImportModal.css
+++ b/packages/dashboard/app/components/GitHubImportModal.css
@@ -313,23 +313,27 @@ Transient inline toast confirming issue close. Sits directly under the preview h
align-items: center;
}
-/* Tab styles for GitHub Import Modal */
+/*
+FNXC:ImportTasks 2026-06-22-12:45:
+The Import Tasks sub-header tab bar should match the Artifacts view's button bar: plain body row, tokenized border/surface buttons, todo-accent active state, and no card-like filled strip under the main header.
+*/
.github-import-tabs {
display: flex;
- gap: var(--space-xs);
- padding: var(--space-sm) var(--space-md);
- border-bottom: 1px solid var(--border);
- background: var(--surface);
+ align-items: center;
+ gap: var(--space-sm);
+ padding: 0;
+ border-bottom: none;
+ background: transparent;
}
.github-import-tab {
- display: flex;
+ display: inline-flex;
align-items: center;
- gap: var(--space-xs);
- padding: var(--space-sm) var(--space-md);
- border: 1px solid transparent;
+ gap: var(--space-sm);
+ padding: var(--space-xs) var(--space-sm);
+ border: 1px solid var(--border);
border-radius: var(--radius-md);
- background: transparent;
+ background: var(--surface);
color: var(--text-muted);
font-size: 13px;
font-weight: 500;
@@ -338,14 +342,14 @@ Transient inline toast confirming issue close. Sits directly under the preview h
}
.github-import-tab:hover:not(:disabled) {
- background: var(--surface-hover);
+ background: var(--card-hover);
color: var(--text);
}
.github-import-tab.active {
- background: var(--card);
- border-color: var(--border);
- color: var(--text);
+ color: var(--todo);
+ border-color: var(--todo);
+ background: color-mix(in srgb, var(--todo) 12%, transparent);
}
.github-import-tab:disabled {
@@ -1598,4 +1602,3 @@ body. Let the content take its full intrinsic height and hand vertical scrolling
}
}
-
diff --git a/packages/dashboard/app/components/MailboxModal.css b/packages/dashboard/app/components/MailboxModal.css
index 690ffa55fa..56cd0005f6 100644
--- a/packages/dashboard/app/components/MailboxModal.css
+++ b/packages/dashboard/app/components/MailboxModal.css
@@ -681,13 +681,16 @@ than overflow the message column. Theme tokens only — no hard-coded colors.
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). */
+/*
+FNXC:MailboxView 2026-06-22-12:58:
+The full-page Mailbox inner view should match Chat's page body: the shared ViewHeader owns the top chrome, and the content area is a flush split row with no extra outer padding. Keep padding on the Mailbox modal, but remove it from the page-scoped .mailbox-content so the left message-list pane aligns like Chat's sidebar.
+*/
.mailbox-view .mailbox-content {
flex: 1;
min-height: 0;
overflow-x: hidden;
overflow-y: auto;
- padding: 0 var(--space-xl) var(--space-xl);
+ padding: 0;
max-height: none;
}
@@ -751,10 +754,7 @@ Tighten the mailbox divider's static visible line to a single 1px var(--border)
.mailbox-view .mailbox-split-detail-pane {
min-height: 0;
overflow-y: auto;
- border: var(--btn-border-width) solid var(--border);
- border-radius: var(--radius-md);
background: var(--surface);
- padding: var(--space-md);
}
/*
@@ -763,6 +763,10 @@ The list pane is fixed to its inline `width` (`flex: 0 0 auto`) so the divider d
*/
.mailbox-view .mailbox-split-list-pane {
flex: 0 0 auto;
+ min-width: 0;
+ max-width: 500px;
+ border-right: var(--btn-border-width) solid var(--border);
+ background: var(--bg-secondary);
}
.mailbox-view .mailbox-split-detail-pane {
@@ -771,6 +775,7 @@ The list pane is fixed to its inline `width` (`flex: 0 0 auto`) so the divider d
min-width: 0;
flex-direction: column;
gap: var(--space-md);
+ padding: var(--space-lg);
}
.mailbox-view .mailbox-split-empty {
diff --git a/packages/dashboard/app/components/ScriptsModal.css b/packages/dashboard/app/components/ScriptsModal.css
index 1bbce840a6..f556c76ef8 100644
--- a/packages/dashboard/app/components/ScriptsModal.css
+++ b/packages/dashboard/app/components/ScriptsModal.css
@@ -114,27 +114,30 @@ Non-Command-Center dashboard CSS must use the canonical --text token. The legacy
min-height: 0;
}
-/* Scheduling scope selector */
+/*
+FNXC:Automations 2026-06-22-12:45:
+The Automations sub-header scope button bar should match the Artifacts tab bar style: each scope is a standalone bordered surface button with the same todo-accent active state, not a segmented control inside a filled capsule.
+*/
.scheduling-scope-selector {
display: flex;
align-items: center;
- gap: var(--space-xs);
- background: var(--surface);
- border: 1px solid var(--border);
- border-radius: var(--radius-md);
- padding: var(--space-xs);
+ gap: var(--space-sm);
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ padding: 0;
}
.scope-btn {
display: inline-flex;
align-items: center;
- gap: var(--space-xs);
+ gap: var(--space-sm);
padding: var(--space-xs) var(--space-md);
font-size: 0.75rem;
font-weight: 500;
- border: none;
- border-radius: var(--radius-sm);
- background: transparent;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ background: var(--surface);
color: var(--text-muted);
cursor: pointer;
transition: background-color var(--transition-fast), color var(--transition-fast);
@@ -146,8 +149,9 @@ Non-Command-Center dashboard CSS must use the canonical --text token. The legacy
}
.scope-btn.active {
- background: var(--todo);
- color: var(--cta-text);
+ color: var(--todo);
+ border-color: var(--todo);
+ background: color-mix(in srgb, var(--todo) 12%, transparent);
}
.scope-btn:focus-visible {
diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.css b/packages/dashboard/app/components/WorkflowNodeEditor.css
index 08eb5eed2a..05f0dfbf82 100644
--- a/packages/dashboard/app/components/WorkflowNodeEditor.css
+++ b/packages/dashboard/app/components/WorkflowNodeEditor.css
@@ -47,12 +47,20 @@ 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. */
+/*
+FNXC:WorkflowEditorEmbedding 2026-06-22-01:00:
+Align the embedded workflows header to the shared ViewHeader/Insights metric — var(--surface) background, --view-header-min-height, --space-lg/--space-xl padding, a single divider, and a 20px todo-tinted icon + 1.125rem title — so Workflows reads the same size and color as Insights.
+*/
.wf-editor-header {
+ box-sizing: border-box;
display: flex;
+ flex-shrink: 0;
align-items: center;
justify-content: space-between;
- padding: var(--space-lg);
+ gap: var(--space-sm);
+ min-height: var(--view-header-min-height);
+ padding: var(--space-lg) var(--space-xl);
+ background: var(--surface);
border-bottom: 1px solid var(--border);
}
@@ -60,12 +68,25 @@ embedded view.
display: flex;
align-items: center;
gap: var(--space-sm);
+ min-width: 0;
margin: 0;
font-size: 1.125rem;
font-weight: 600;
color: var(--text);
}
+.wf-editor-header h2 svg {
+ flex-shrink: 0;
+ color: var(--todo);
+}
+
+.wf-editor-header h2 span {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.wf-editor-close {
display: inline-flex;
align-items: center;
@@ -134,6 +155,9 @@ FNXC:WorkflowSidebar 2026-06-22-12:00:
Workflow authors need to reclaim horizontal graph-editing space without leaving
the workflow view. Hide only the sidebar shell when collapsed and keep the
restore control attached to the canvas so the state is always reversible.
+
+FNXC:WorkflowSidebar 2026-06-22-12:35:
+When collapsed, the show-sidebar control belongs inline in the workflow name strip before the workflow name. It must be icon-only and in normal document flow so it cannot overlap the header title or workflow name.
*/
.wf-editor-body--sidebar-collapsed .wf-editor-sidebar {
display: none;
@@ -179,10 +203,9 @@ restore control attached to the canvas so the state is always reversible.
}
.wf-sidebar-shell-restore {
- position: absolute;
- top: var(--space-sm);
- left: var(--space-sm);
- z-index: 6;
+ flex: 0 0 auto;
+ width: 30px;
+ padding-inline: 0;
white-space: nowrap;
}
@@ -1416,7 +1439,7 @@ Built-in workflow prompts need visible override state and a reset action without
/* Inline name + description strip (KTD-10). */
.wf-name-strip {
display: flex;
- align-items: baseline;
+ align-items: center;
gap: var(--space-sm);
padding: var(--space-xs) var(--space-sm);
border-bottom: 1px solid var(--border);
diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.tsx b/packages/dashboard/app/components/WorkflowNodeEditor.tsx
index 857984bb38..243278a932 100644
--- a/packages/dashboard/app/components/WorkflowNodeEditor.tsx
+++ b/packages/dashboard/app/components/WorkflowNodeEditor.tsx
@@ -2754,20 +2754,6 @@ function InnerEditor({
- {sidebarCollapsed && !isMobileMode && (
- setSidebarCollapsed(false)}
- >
-
- {t("workflows.sidebar", "Workflows")}
-
- )}
+ {sidebarCollapsed && !isMobileMode && (
+ setSidebarCollapsed(false)}
+ >
+
+
+ )}
{isBuiltin ? (
{activeWorkflow.name}
diff --git a/packages/dashboard/app/components/__tests__/ChatView.test.tsx b/packages/dashboard/app/components/__tests__/ChatView.test.tsx
index d0c46c9899..77e1c92e12 100644
--- a/packages/dashboard/app/components/__tests__/ChatView.test.tsx
+++ b/packages/dashboard/app/components/__tests__/ChatView.test.tsx
@@ -2800,16 +2800,16 @@ describe("ChatView", () => {
const toggle = screen.getByTestId("chat-thread-render-toggle");
const providerIcon = identity.querySelector(".provider-icon");
const modelTag = identity.querySelector(".chat-model-tag");
- const newChatButton = screen.getByTestId("chat-thread-new-chat-btn");
+ const newChatButton = screen.getByTestId("chat-new-btn");
expect(header).toBeInTheDocument();
+ expect(newChatButton.closest(".view-header")).toBeInTheDocument();
expect(providerIcon).toBeInTheDocument();
expect(within(identity).getByText("Agent Chat")).toBeInTheDocument();
expect(modelTag).toBeInTheDocument();
expect(modelTag).toHaveTextContent("Claude Sonnet 4.5");
expect(toggle).toBeInTheDocument();
- expect(header?.children[header.children.length - 2]).toBe(toggle);
- expect(header?.children[header.children.length - 1]).toBe(newChatButton);
+ expect(header?.children[header.children.length - 1]).toBe(toggle);
expect(document.querySelectorAll(".chat-thread-header .chat-model-tag")).toHaveLength(1);
});
@@ -3445,7 +3445,8 @@ describe("ChatView sidebar structure", () => {
expect(document.querySelector(".chat-sidebar")).toBeInTheDocument();
expect(document.querySelector(".chat-sidebar-search")).toBeInTheDocument();
expect(document.querySelector(".chat-sidebar-list")).toBeInTheDocument();
- expect(document.querySelector(".chat-sidebar-footer")).toBeInTheDocument();
+ expect(document.querySelector(".chat-sidebar-footer")).not.toBeInTheDocument();
+ expect(screen.getByTestId("chat-new-btn").closest(".view-header")).toBeInTheDocument();
expect(document.querySelector(".chat-sidebar-header")).not.toBeInTheDocument();
});
@@ -3994,30 +3995,31 @@ describe("resizable sidebar", () => {
});
});
-describe("thread header New Chat button", () => {
+describe("Chat header New Chat button", () => {
const activeSession = { id: "session-001", agentId: "agent-001", status: "active", title: "Test Chat", createdAt: "2026-04-08T00:00:00.000Z", updatedAt: "2026-04-08T00:00:00.000Z" };
- it("renders New Chat button in thread header on desktop when session is active", async () => {
+ it("renders New Chat button in the shared header on desktop when session is active", async () => {
const viewportSpy = mockViewportMode("desktop");
setupMockChat({ activeSession });
await renderWithAct( );
- const btn = screen.getByTestId("chat-thread-new-chat-btn");
+ const btn = screen.getByTestId("chat-new-btn");
expect(btn).toBeInTheDocument();
+ expect(btn.closest(".view-header")).toBeInTheDocument();
expect(btn).toHaveTextContent("New Chat");
expect(btn).toHaveClass("btn", "btn-sm", "btn-primary");
viewportSpy.mockRestore();
});
- it("clicking thread header New Chat button opens the NewChatDialog", async () => {
+ it("clicking shared header New Chat button opens the NewChatDialog", async () => {
const viewportSpy = mockViewportMode("desktop");
setupMockChat({ activeSession });
await renderWithAct( );
- const btn = screen.getByTestId("chat-thread-new-chat-btn");
+ const btn = screen.getByTestId("chat-new-btn");
await act(async () => {
fireEvent.click(btn);
});
@@ -4027,13 +4029,14 @@ describe("thread header New Chat button", () => {
viewportSpy.mockRestore();
});
- it("does not render New Chat button in thread header on mobile", async () => {
+ it("does not render New Chat button in the shared header on mobile", async () => {
const viewportSpy = mockViewportMode("mobile");
setupMockChat({ activeSession });
await renderWithAct( );
expect(screen.queryByTestId("chat-thread-new-chat-btn")).toBeNull();
+ expect(document.querySelector(".view-header [data-testid='chat-new-btn']")).toBeNull();
viewportSpy.mockRestore();
});
@@ -5568,6 +5571,18 @@ describe("ChatView mobile CSS contract", () => {
expect(mobileRuleNotContains(".chat-sidebar", "max-height: 40vh")).toBe(true);
});
+ it("keeps the shared header outside the bounded chat body row", async () => {
+ const viewRule = css.match(/\.chat-view\s*\{([^}]*)\}/)?.[1] ?? "";
+ const bodyRule = css.match(/\.chat-view__body\s*\{([^}]*)\}/)?.[1] ?? "";
+
+ expect(viewRule).toContain("flex-direction: column;");
+ expect(viewRule).toContain("min-height: 0;");
+ expect(bodyRule).toContain("display: flex;");
+ expect(bodyRule).toContain("flex: 1 1 auto;");
+ expect(bodyRule).toContain("min-height: 0;");
+ expect(bodyRule).toContain("overflow: hidden;");
+ });
+
it("mobile .chat-sidebar-header is hidden", async () => {
expect(mobileRuleContains(".chat-sidebar-header", "display: none")).toBe(true);
});
diff --git a/packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx b/packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx
index 39d9143050..538e4be44e 100644
--- a/packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx
+++ b/packages/dashboard/app/components/__tests__/GitHubImportModal.test.tsx
@@ -95,6 +95,21 @@ describe("GitHubImportModal", () => {
expect(source).toContain(".github-import-preview-pane.mobile.active .github-import-pane-content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n overscroll-behavior: contain;");
});
+ it("styles import type tabs like the Artifacts button bar", () => {
+ const source = readFileSync(resolve(__dirname, "../GitHubImportModal.css"), "utf8");
+ const tabsRule = source.match(/\.github-import-tabs\s*\{[^}]*\}/)?.[0] ?? "";
+ const tabRule = source.match(/\.github-import-tab\s*\{[^}]*\}/)?.[0] ?? "";
+ const activeRule = source.match(/\.github-import-tab\.active\s*\{[^}]*\}/)?.[0] ?? "";
+
+ expect(tabsRule).toContain("background: transparent;");
+ expect(tabsRule).toContain("border-bottom: none;");
+ expect(tabRule).toContain("border: 1px solid var(--border);");
+ expect(tabRule).toContain("background: var(--surface);");
+ expect(activeRule).toContain("color: var(--todo);");
+ expect(activeRule).toContain("border-color: var(--todo);");
+ expect(activeRule).toContain("background: color-mix(in srgb, var(--todo) 12%, transparent);");
+ });
+
beforeEach(() => {
vi.clearAllMocks();
vi.mocked(fetchGitRemotes).mockReset();
diff --git a/packages/dashboard/app/components/__tests__/MailboxView.test.tsx b/packages/dashboard/app/components/__tests__/MailboxView.test.tsx
index f2fb613c9d..f939184567 100644
--- a/packages/dashboard/app/components/__tests__/MailboxView.test.tsx
+++ b/packages/dashboard/app/components/__tests__/MailboxView.test.tsx
@@ -1855,6 +1855,7 @@ describe("MailboxView", () => {
expect(contentBlock).toContain("min-height: 0;");
expect(contentBlock).toContain("overflow-y: auto;");
expect(contentBlock).toContain("max-height: none;");
+ expect(contentBlock).toContain("padding: 0;");
});
it("defines desktop/tablet split-pane selectors under .mailbox-view scope", async () => {
@@ -1867,19 +1868,25 @@ describe("MailboxView", () => {
expect(splitPaneBlockMatch).toBeTruthy();
const splitPaneBlock = splitPaneBlockMatch![1];
expect(splitPaneBlock).toContain("overflow-y: auto;");
- expect(splitPaneBlock).toContain("border: var(--btn-border-width) solid var(--border);");
expect(splitPaneBlock).toContain("background: var(--surface);");
+ expect(splitPaneBlock).not.toContain("border: var(--btn-border-width) solid var(--border);");
+ expect(splitPaneBlock).not.toContain("border-radius: var(--radius-md);");
- // FNXC:Mailbox 2026-06-22-18:05: list pane fixed to inline width; detail pane fills remainder and may shrink below content.
+ // FNXC:MailboxView 2026-06-22-12:58: full-page Mailbox list pane mirrors Chat's left sidebar surface and spacing.
const listPaneBlockMatch = css.match(/\.mailbox-view\s+\.mailbox-split-list-pane\s*\{([^}]*)\}/);
expect(listPaneBlockMatch).toBeTruthy();
expect(listPaneBlockMatch![1]).toContain("flex: 0 0 auto;");
+ expect(listPaneBlockMatch![1]).toContain("min-width: 0;");
+ expect(listPaneBlockMatch![1]).toContain("max-width: 500px;");
+ expect(listPaneBlockMatch![1]).toContain("border-right: var(--btn-border-width) solid var(--border);");
+ expect(listPaneBlockMatch![1]).toContain("background: var(--bg-secondary);");
// Match the standalone detail-pane rule (the one declaring `display: flex;`), not the shared border/background block.
const detailPaneBlockMatch = css.match(/\.mailbox-view\s+\.mailbox-split-detail-pane\s*\{([^}]*display:\s*flex;[^}]*)\}/);
expect(detailPaneBlockMatch).toBeTruthy();
expect(detailPaneBlockMatch![1]).toContain("flex: 1 1 auto;");
expect(detailPaneBlockMatch![1]).toContain("min-width: 0;");
+ expect(detailPaneBlockMatch![1]).toContain("padding: var(--space-lg);");
const resizeHandleBlockMatch = css.match(/\.mailbox-view\s+\.mailbox-split-resize-handle\s*\{([^}]*)\}/);
expect(resizeHandleBlockMatch).toBeTruthy();
diff --git a/packages/dashboard/app/components/__tests__/ScheduledTasksModal.test.tsx b/packages/dashboard/app/components/__tests__/ScheduledTasksModal.test.tsx
index 9bd8472f99..8d2952e741 100644
--- a/packages/dashboard/app/components/__tests__/ScheduledTasksModal.test.tsx
+++ b/packages/dashboard/app/components/__tests__/ScheduledTasksModal.test.tsx
@@ -1,5 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
import { ScheduledTasksModal } from "../ScheduledTasksModal";
import type { Routine } from "@fusion/core";
@@ -167,6 +169,21 @@ describe("ScheduledTasksModal", () => {
expect(toolbarRight?.contains(newAutomationButton)).toBe(true);
});
+ it("styles scope controls like the Artifacts button bar", () => {
+ const source = readFileSync(resolve(__dirname, "../ScriptsModal.css"), "utf8");
+ const selectorRule = source.match(/\.scheduling-scope-selector\s*\{[^}]*\}/)?.[0] ?? "";
+ const scopeRule = source.match(/\.scope-btn\s*\{[^}]*\}/)?.[0] ?? "";
+ const activeRule = source.match(/\.scope-btn\.active\s*\{[^}]*\}/)?.[0] ?? "";
+
+ expect(selectorRule).toContain("background: transparent;");
+ expect(selectorRule).toContain("border: none;");
+ expect(scopeRule).toContain("border: 1px solid var(--border);");
+ expect(scopeRule).toContain("background: var(--surface);");
+ expect(activeRule).toContain("color: var(--todo);");
+ expect(activeRule).toContain("border-color: var(--todo);");
+ expect(activeRule).toContain("background: color-mix(in srgb, var(--todo) 12%, transparent);");
+ });
+
it("uses routine APIs with global scope by default", async () => {
render( );
diff --git a/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts b/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts
index fcc0d9c903..1a3280463d 100644
--- a/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts
+++ b/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.css.test.ts
@@ -42,6 +42,21 @@ function expectNoHardcodedWhiteBackground(rule: string): void {
}
describe("WorkflowNodeEditor themed React Flow CSS contract", () => {
+ it("matches the shared Insights/ViewHeader chrome", () => {
+ const editorCss = readComponentCss("WorkflowNodeEditor.css");
+ const headerRule = findRule([editorCss], /\.wf-editor-header\s*\{[^}]*\}/);
+ const titleRule = findRule([editorCss], /\.wf-editor-header h2\s*\{[^}]*\}/);
+ const iconRule = findRule([editorCss], /\.wf-editor-header h2 svg\s*\{[^}]*\}/);
+
+ expect(headerRule).toMatch(/min-height\s*:\s*var\(--view-header-min-height\)\s*;/);
+ expect(headerRule).toMatch(/padding\s*:\s*var\(--space-lg\) var\(--space-xl\)\s*;/);
+ expect(headerRule).toMatch(/background\s*:\s*var\(--surface\)\s*;/);
+ expect(headerRule).toMatch(/border-bottom\s*:\s*1px solid var\(--border\)\s*;/);
+ expect(titleRule).toMatch(/font-size\s*:\s*1\.125rem\s*;/);
+ expect(titleRule).toMatch(/font-weight\s*:\s*600\s*;/);
+ expect(iconRule).toMatch(/color\s*:\s*var\(--todo\)\s*;/);
+ });
+
it("FN-6701 themes zoom controls, mini-map, and sidebar checkboxes with tokens", () => {
const baseCss = loadAllAppCssBaseOnly();
@@ -146,8 +161,10 @@ describe("WorkflowNodeEditor sidebar overflow CSS contract", () => {
expect(collapsedSidebarRule).toMatch(/display\s*:\s*none\s*;/);
const restoreRule = findRule([editorCss], /\.wf-sidebar-shell-restore\s*\{[^}]*\}/);
- expect(restoreRule).toMatch(/position\s*:\s*absolute\s*;/);
- expect(restoreRule).toMatch(/left\s*:\s*var\(--space-sm\)\s*;/);
+ expect(restoreRule).not.toMatch(/position\s*:\s*absolute\s*;/);
+ expect(restoreRule).toMatch(/flex\s*:\s*0 0 auto\s*;/);
+ expect(restoreRule).toMatch(/width\s*:\s*30px\s*;/);
+ expect(restoreRule).toMatch(/padding-inline\s*:\s*0\s*;/);
expect(restoreRule).toMatch(/white-space\s*:\s*nowrap\s*;/);
});
diff --git a/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx b/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx
index 3f4cf17fe9..43be0035a8 100644
--- a/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx
+++ b/packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx
@@ -453,7 +453,10 @@ describe("WorkflowNodeEditor", () => {
fireEvent.click(screen.getByTestId("wf-sidebar-collapse"));
expect(body!).toHaveClass("wf-editor-body--sidebar-collapsed");
- expect(screen.getByTestId("wf-sidebar-restore")).toHaveTextContent("Workflows");
+ const restoreButton = screen.getByTestId("wf-sidebar-restore");
+ expect(restoreButton).toHaveAccessibleName("Show workflow sidebar");
+ expect(restoreButton).toHaveTextContent("");
+ expect(screen.getByTestId("wf-workflow-name").previousElementSibling).toBe(restoreButton);
fireEvent.click(screen.getByTestId("wf-sidebar-restore"));
diff --git a/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css b/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css
index 448ae21bd6..ce560d8024 100644
--- a/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css
+++ b/plugins/fusion-plugin-compound-engineering/src/dashboard/CompoundEngineeringView.css
@@ -6,6 +6,9 @@ The Compound Engineering dashboard view must follow the dashboard design scale s
/*
FNXC:CompoundEngineeringUI 2026-06-22-09:40:
The root .ce-view is a flex column hosting the flex-shrink:0 shared ViewHeader on top and a single scrolling .ce-view-body below. ViewHeader owns the --space-lg top/side padding (so the root no longer pads its own header row); the body re-adds matching side/bottom padding so content keeps the same horizontal rhythm and there is no double top padding.
+
+FNXC:CompoundEngineeringUI 2026-06-22-12:35:
+The shared ViewHeader sits directly above plugin content, so the body needs top padding too; otherwise the first Compound Engineering card/flow row bumps against the header divider. Use the same tokenized vertical rhythm as the dashboard's native artifact and goals views.
*/
.ce-view {
display: flex;
@@ -27,7 +30,7 @@ The root .ce-view is a flex column hosting the flex-shrink:0 shared ViewHeader o
gap: var(--space-lg);
min-width: 0;
min-height: 0;
- padding: 0 calc(var(--space-lg) + var(--space-xs)) var(--space-lg);
+ padding: var(--space-lg) calc(var(--space-lg) + var(--space-xs)) var(--space-lg);
box-sizing: border-box;
overflow: auto;
}