From ee9c8ab7d54b0017d7891010809ddec1306da239 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 22 Jun 2026 12:43:32 -0700 Subject: [PATCH] fix(dashboard): align view headers and pane spacing --- .changeset/dashboard-view-chrome-alignment.md | 3 + .../dashboard/app/components/ChatView.css | 20 ++++--- .../dashboard/app/components/ChatView.tsx | 56 +++++++++++-------- .../app/components/GitHubImportModal.css | 33 ++++++----- .../dashboard/app/components/MailboxModal.css | 15 +++-- .../dashboard/app/components/ScriptsModal.css | 28 ++++++---- .../app/components/WorkflowNodeEditor.css | 37 +++++++++--- .../app/components/WorkflowNodeEditor.tsx | 27 +++++---- .../components/__tests__/ChatView.test.tsx | 35 ++++++++---- .../__tests__/GitHubImportModal.test.tsx | 15 +++++ .../components/__tests__/MailboxView.test.tsx | 11 +++- .../__tests__/ScheduledTasksModal.test.tsx | 17 ++++++ .../__tests__/WorkflowNodeEditor.css.test.ts | 21 ++++++- .../__tests__/WorkflowNodeEditor.test.tsx | 5 +- .../src/dashboard/CompoundEngineeringView.css | 5 +- 15 files changed, 228 insertions(+), 100 deletions(-) create mode 100644 .changeset/dashboard-view-chrome-alignment.md 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 ) : ( -
- -
+ isMobile ? ( +
+ +
+ ) : null )}
@@ -3890,17 +3910,6 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView {showAllAsPlain ? : } )} - {!isMobile && ( - - )} -
)} @@ -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 && ( - - )} + )} {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; }