fix(dashboard): align view headers and pane spacing
This commit is contained in:
3
.changeset/dashboard-view-chrome-alignment.md
Normal file
3
.changeset/dashboard-view-chrome-alignment.md
Normal file
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
<div className="chat-view">
|
||||
<ViewHeader
|
||||
icon={MessageSquare}
|
||||
title={t("chat.title", "Chat")}
|
||||
actions={
|
||||
!isMobile ? (
|
||||
<button
|
||||
className="btn btn-sm btn-primary chat-view-header-new-chat"
|
||||
onClick={() => setShowNewDialog(true)}
|
||||
data-testid="chat-new-btn"
|
||||
>
|
||||
<Plus size={14} />
|
||||
{t("chat.newChat", "New Chat")}
|
||||
</button>
|
||||
) : null
|
||||
}
|
||||
/>
|
||||
<div className="chat-view__body">
|
||||
{/* Sidebar */}
|
||||
<div
|
||||
className={`chat-sidebar${!sidebarVisible ? " chat-sidebar--hidden" : ""}`}
|
||||
@@ -3400,16 +3418,18 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
||||
</div>
|
||||
) : null
|
||||
) : (
|
||||
<div className="chat-sidebar-footer">
|
||||
<button
|
||||
className="btn btn-sm btn-primary chat-sidebar-footer-btn"
|
||||
onClick={() => setShowNewDialog(true)}
|
||||
data-testid="chat-new-btn"
|
||||
>
|
||||
<Plus size={14} />
|
||||
{t("chat.newChat", "New Chat")}
|
||||
</button>
|
||||
</div>
|
||||
isMobile ? (
|
||||
<div className="chat-sidebar-footer">
|
||||
<button
|
||||
className="btn btn-sm btn-primary chat-sidebar-footer-btn"
|
||||
onClick={() => setShowNewDialog(true)}
|
||||
data-testid="chat-new-btn"
|
||||
>
|
||||
<Plus size={14} />
|
||||
{t("chat.newChat", "New Chat")}
|
||||
</button>
|
||||
</div>
|
||||
) : null
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -3890,17 +3910,6 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
||||
{showAllAsPlain ? <EyeOff size={14} /> : <Eye size={14} />}
|
||||
</button>
|
||||
)}
|
||||
{!isMobile && (
|
||||
<button
|
||||
className="btn btn-sm btn-primary chat-thread-header-new-chat"
|
||||
onClick={() => setShowNewDialog(true)}
|
||||
data-testid="chat-thread-new-chat-btn"
|
||||
>
|
||||
<Plus size={14} />
|
||||
{t("chat.newChat", "New Chat")}
|
||||
</button>
|
||||
)}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -3953,6 +3962,7 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* New Chat Dialog (rendered at root level) */}
|
||||
{showNewDialog && (
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2754,20 +2754,6 @@ function InnerEditor({
|
||||
</aside>
|
||||
|
||||
<section className="wf-editor-canvas-wrap">
|
||||
{sidebarCollapsed && !isMobileMode && (
|
||||
<button
|
||||
type="button"
|
||||
className="wf-sidebar-shell-restore"
|
||||
data-testid="wf-sidebar-restore"
|
||||
aria-expanded="false"
|
||||
aria-label={t("workflows.showSidebar", "Show workflow sidebar")}
|
||||
title={t("workflows.showSidebar", "Show workflow sidebar")}
|
||||
onClick={() => setSidebarCollapsed(false)}
|
||||
>
|
||||
<ChevronRight size={14} aria-hidden />
|
||||
<span>{t("workflows.sidebar", "Workflows")}</span>
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="wf-editor-mobile-back"
|
||||
@@ -2783,6 +2769,19 @@ function InnerEditor({
|
||||
plain text (no click affordance); user-owned workflows are
|
||||
click-to-edit (Enter commits, Escape cancels, blur commits). */}
|
||||
<div className="wf-name-strip">
|
||||
{sidebarCollapsed && !isMobileMode && (
|
||||
<button
|
||||
type="button"
|
||||
className="wf-sidebar-shell-restore"
|
||||
data-testid="wf-sidebar-restore"
|
||||
aria-expanded="false"
|
||||
aria-label={t("workflows.showSidebar", "Show workflow sidebar")}
|
||||
title={t("workflows.showSidebar", "Show workflow sidebar")}
|
||||
onClick={() => setSidebarCollapsed(false)}
|
||||
>
|
||||
<ChevronRight size={14} aria-hidden />
|
||||
</button>
|
||||
)}
|
||||
{isBuiltin ? (
|
||||
<span className="wf-workflow-name wf-workflow-name--readonly" data-testid="wf-workflow-name">
|
||||
{activeWorkflow.name}
|
||||
|
||||
@@ -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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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(<ChatView projectId="proj-123" addToast={vi.fn()} />);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(<ScheduledTasksModal onClose={onClose} addToast={addToast} />);
|
||||
|
||||
|
||||
@@ -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*;/);
|
||||
});
|
||||
|
||||
|
||||
@@ -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"));
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user