fix(FN-6950): polish dashboard modal and chat chrome

This commit is contained in:
gsxdsm
2026-06-23 00:20:55 -07:00
parent 826a7eea31
commit 4ed84be7d1
15 changed files with 281 additions and 36 deletions

View File

@@ -0,0 +1,5 @@
---
"@runfusion/fusion": patch
---
Polish mobile workflow header alignment, task chat provider icons, modal overlay chrome, and shadcn font consistency.

View File

@@ -43,6 +43,30 @@ describe("Shadcn Gray color theme", () => {
expect(dividerBlock).not.toContain("border-left-color");
});
it("pins shadcn-family UI controls to one font family while preserving mono content", () => {
const uiFontBlock = extractGroupedRuleBlock(themeData, '[data-color-theme^="shadcn"],');
const monoFontBlock = extractGroupedRuleBlock(themeData, '[data-color-theme^="shadcn"] code');
expect(uiFontBlock).toContain('[data-color-theme^="shadcn"] button');
expect(uiFontBlock).toContain('[data-color-theme^="shadcn"] input');
expect(uiFontBlock).toContain('[data-color-theme^="shadcn"] select');
expect(uiFontBlock).toContain('[data-color-theme^="shadcn"] textarea');
expect(uiFontBlock).toContain('[data-color-theme^="shadcn"] .modal');
expect(uiFontBlock).toContain("font-family: var(--font-primary);");
expect(monoFontBlock).toContain('[data-color-theme^="shadcn"] pre');
expect(monoFontBlock).toContain('[data-color-theme^="shadcn"] .font-mono');
expect(monoFontBlock).toContain("font-family: var(--font-mono);");
});
it("keeps glass theme modal overlays transparent and non-blurring", () => {
const glassModalOverlayBlock = extractSelectorBlock(themeData, '[data-color-theme="glass"] .modal-overlay');
expect(glassModalOverlayBlock).toContain("background: transparent;");
expect(glassModalOverlayBlock).toContain("backdrop-filter: none;");
expect(glassModalOverlayBlock).toContain("-webkit-backdrop-filter: none;");
expect(glassModalOverlayBlock).not.toContain("blur(");
});
it("registers Shadcn Gray in core, dashboard options, and the dashboard bootstrap validator", () => {
expect(CORE_COLOR_THEMES).toContain("shadcn-gray");
expect(DASHBOARD_COLOR_THEMES).toContainEqual({

View File

@@ -7,21 +7,17 @@ interface SelectorExpectation {
}
const convertedSelectors: SelectorExpectation[] = [
{
selector: ".modal-overlay",
expectedColorMix: "color-mix(in srgb, var(--text) 60%, transparent)",
},
{
selector: ".modal-header",
expectedColorMix: "color-mix(in srgb, var(--text) 10%, transparent)",
expectedColorMix: "color-mix(in srgb, var(--surface) 80%, transparent)",
},
{
selector: ".modal-actions",
expectedColorMix: "color-mix(in srgb, var(--text) 5%, transparent)",
expectedColorMix: "color-mix(in srgb, var(--surface) 60%, transparent)",
},
{
selector: ".settings-sidebar",
expectedColorMix: "color-mix(in srgb, var(--text) 10%, transparent)",
expectedColorMix: "color-mix(in srgb, var(--surface) 60%, transparent)",
},
{
selector: ".step-progress-segment[data-tooltip]:hover::after",
@@ -106,6 +102,18 @@ describe("styles.css rgba tokenization", () => {
expect(nonTokenRgbaLines(loadStylesCss())).toEqual([]);
});
it("keeps shared modal overlays visually transparent while panels provide shadow depth", () => {
const css = loadStylesCss();
const overlayBlock = extractSelectorBlocks(css, ".modal-overlay").at(0) ?? "";
const modalBlock = extractSelectorBlocks(css, ".modal").at(0) ?? "";
expect(overlayBlock).toContain("background: transparent;");
expect(overlayBlock).toContain("backdrop-filter: none;");
expect(overlayBlock).toContain("-webkit-backdrop-filter: none;");
expect(overlayBlock).not.toContain("color-mix(in srgb, var(--text)");
expect(modalBlock).toContain("box-shadow: var(--shadow-lg);");
});
it("keeps converted selectors on tokenized color-mix() values", () => {
const css = loadStylesCss();

View File

@@ -134,12 +134,20 @@ non-notched devices, so this is a no-op there. Pair with viewport-fit=cover (ind
/*
FNXC:WorkflowControls 2026-06-23-20:05:
Mobile places the workflow dropdown in the top header beside the logo/project switch. Match the 32px board/list toggle height, reduce row gaps, hide the text label/counts, and cap width aggressively so resizing does not crowd adjacent header controls.
FNXC:WorkflowControls 2026-06-23-20:58:
In narrow mobile board/list views the workflow selector should sit centered in the available space between the project dropdown and the board/list toggle, while the trigger height lines up exactly with the 32px view toggle. Let the slot flex across the remaining header row and clamp the trigger to a wider 104px-128px range so short names such as "Coding" render fully when the screen can support it.
FNXC:WorkflowControls 2026-06-23-21:12:
The mobile workflow dropdown must visually match the board/list segmented toggle height exactly. Pin the toolbar wrapper, switcher, and trigger to the same 32px border-box and remove inherited vertical padding/line-height drift so the top and bottom edges align pixel-for-pixel.
*/
.header {
gap: var(--space-xs);
gap: var(--space-sm);
align-items: center;
}
.header-left {
flex: 1 1 auto;
min-width: 0;
gap: var(--space-xs);
}
@@ -150,24 +158,35 @@ non-notched devices, so this is a no-op there. Pair with viewport-fit=cover (ind
}
.header-workflow-slot {
flex: 0 1 min(30vw, calc(var(--space-2xl) * 2.875));
justify-content: flex-start;
min-width: calc(var(--space-2xl) * 2);
max-width: min(30vw, calc(var(--space-2xl) * 2.875));
flex: 1 1 auto;
justify-content: center;
min-width: 0;
max-width: none;
}
.header-actions {
gap: var(--space-xs);
flex: 0 0 auto;
align-items: center;
gap: var(--space-sm);
}
.header-workflow-slot:empty {
display: none;
}
.header-workflow-slot .workflow-switcher {
width: 100%;
.header-workflow-slot .board-workflow-toolbar,
.header-workflow-slot .list-workflow-control {
height: 32px;
align-items: center;
}
.header-workflow-slot .workflow-switcher {
width: clamp(calc(var(--space-2xl) * 3.25), 36vw, calc(var(--space-2xl) * 4));
height: 32px;
max-height: 32px;
min-width: 0;
flex: 0 1 auto;
align-items: center;
}
.header-workflow-slot .workflow-switcher-label {
@@ -175,13 +194,17 @@ non-notched devices, so this is a no-op there. Pair with viewport-fit=cover (ind
}
.header-workflow-slot .workflow-switcher-trigger {
appearance: none;
box-sizing: border-box;
width: 100%;
height: 32px;
max-width: 100%;
min-height: 32px;
max-height: 32px;
padding: 0 var(--space-xs);
font-size: 12px;
line-height: 1;
overflow: hidden;
}
.header-workflow-slot .workflow-switcher-trigger-main {

View File

@@ -19,7 +19,8 @@
/* FNXC:ListView 2026-06-23-23:42: No divider between the controls and quick-add. The controls row now carries action groups only; the aggregate top task count was removed while contextual section counts remain lower in the list.
FNXC:ListView 2026-06-23-23:55: Bulk Edit, View, and New Task are one no-wrap action cluster so the primary list actions stay visually together instead of splitting across opposite edges or separate wrapped lines.
FNXC:ListView 2026-06-23-00:25: The primary action cluster must stay on one physical row even when the list pane narrows; preserve max-content width and let the cluster scroll horizontally instead of wrapping individual actions onto separate lines. */
FNXC:ListView 2026-06-23-00:25: The primary action cluster must stay on one physical row even when the list pane narrows; preserve max-content width and let the cluster scroll horizontally instead of wrapping individual actions onto separate lines.
FNXC:ListView 2026-06-23-21:42: Center the Bulk Edit, View, and New Task cluster in both desktop sidebar controls and the mobile list toolbar so the primary actions read as a single balanced control group. */
.list-sidebar-controls {
display: flex;
flex-direction: column;
@@ -32,7 +33,7 @@ FNXC:ListView 2026-06-23-00:25: The primary action cluster must stay on one phys
.list-sidebar-controls__toolbar {
display: flex;
align-items: center;
justify-content: flex-start;
justify-content: center;
gap: var(--space-sm);
flex-wrap: wrap;
}
@@ -41,7 +42,7 @@ FNXC:ListView 2026-06-23-00:25: The primary action cluster must stay on one phys
.list-sidebar-controls__actions {
display: flex;
align-items: center;
justify-content: flex-start;
justify-content: center;
flex: 0 1 auto;
flex-wrap: nowrap;
gap: var(--space-xs);
@@ -945,6 +946,7 @@ In the split sidebar the title cell must allow the title to wrap to two lines (h
.list-toolbar {
padding: var(--space-md);
flex-wrap: wrap;
justify-content: center;
}
.list-create-area {
@@ -1153,6 +1155,11 @@ In the split sidebar the title cell must allow the title to wrap to two lines (h
padding: var(--space-sm) var(--space-md);
flex-wrap: wrap;
gap: var(--space-sm);
justify-content: center;
}
.list-toolbar .list-action-cluster {
justify-content: center;
}
.list-selection-stats {

View File

@@ -1965,10 +1965,14 @@ queries already apply (the container-query rule above also fires and produces th
/*
FNXC:GitManager 2026-06-23-23:52:
Sidebar-launched floating modals should not dim, blur, or block the app behind them. Match the Files/RightDock floating-window model: the overlay is transparent and click-through, while the Git Manager panel remains interactive. Dismissal stays on Escape/close button instead of backdrop click.
FNXC:GitManager 2026-06-23-21:28:
Theme-level modal backdrop rules can load after component CSS and reapply dim/blur, especially in glass themes. Use a higher-specificity modal overlay selector and clear both standard and WebKit backdrop filters so Git Manager never darkens the app behind it.
*/
.modal-overlay.git-manager-modal-overlay {
.modal-overlay.git-manager-modal-overlay.git-manager-modal-overlay {
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
pointer-events: none;
}

View File

@@ -128,6 +128,24 @@ FN-6425 requires the chat expand control to stay inside the chat view as an icon
flex: 0 0 auto;
}
.task-chat-provider-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 36px;
width: 36px;
height: 36px;
border-radius: var(--radius);
background: color-mix(in srgb, var(--provider-icon-color, currentColor) 8%, transparent);
color: var(--text-muted);
}
.task-chat-provider-icon .provider-icon {
display: inline-flex;
align-items: center;
justify-content: center;
}
.task-chat-role-label {
color: var(--text);
font-weight: 600;

View File

@@ -12,6 +12,7 @@ import { getErrorMessage } from "@fusion/core";
import { linkifyFilePaths } from "../utils/filePathLinkify";
import { formatRelativeTimeAgo } from "../utils/relativeTimeAgo";
import { AgentAvatar } from "./AgentAvatar";
import { ProviderIcon } from "./ProviderIcon";
import { clampChatInputHeight, resolveChatInputOverflowY } from "../utils/chatInputAutosize";
import { markdownComponents } from "./AgentLogViewer";
import "./TaskChatTab.css";
@@ -29,6 +30,11 @@ interface TaskChatTabProps {
type AgentLogRole = AgentRole | undefined;
type TaskChatModelInfo = {
provider: string;
modelId?: string;
};
type UserChatMessage = Pick<SteeringComment, "id" | "text" | "createdAt"> & { optimistic?: boolean };
type TaskChatTranscriptItem =
@@ -97,6 +103,67 @@ function getRoleIcon(role: AgentLogRole): string | undefined {
}
}
function parseModelMarker(entry: AgentLogEntry): TaskChatModelInfo | null {
if (entry.type !== "text") return null;
const match = entry.text.match(/^(?:Triage|Executor|Reviewer) using model: (.+?)\/(.+)$/);
if (!match) return null;
return { provider: match[1], modelId: match[2] };
}
function makeModelInfo(provider: string | undefined, modelId: string | undefined): TaskChatModelInfo | null {
if (!provider) return null;
return modelId ? { provider, modelId } : { provider };
}
function getExplicitModelForRole(task: Task | TaskDetail, role: AgentLogRole): TaskChatModelInfo | null {
if (role === "triage" && task.planningModelProvider) {
return makeModelInfo(task.planningModelProvider, task.planningModelId);
}
if (role === "executor" && task.modelProvider) {
return makeModelInfo(task.modelProvider, task.modelId);
}
if ((role === "reviewer" || role === "merger") && task.validatorModelProvider) {
return makeModelInfo(task.validatorModelProvider, task.validatorModelId);
}
return null;
}
function getRuntimeModelForRole(entries: readonly AgentLogEntry[], role: AgentLogRole): TaskChatModelInfo | null {
for (let index = entries.length - 1; index >= 0; index -= 1) {
const entry = entries[index];
if (entry.agent !== role) continue;
const parsed = parseModelMarker(entry);
if (parsed) return parsed;
}
return null;
}
function getModelForRole(task: Task | TaskDetail, role: AgentLogRole, entries: readonly AgentLogEntry[]): TaskChatModelInfo | null {
/*
FNXC:TaskDetailChat 2026-06-23-21:18:
Task-detail chat agent headers should identify the AI provider actually backing each role, not a generic/role avatar. Prefer explicit task model overrides because they are stable before logs stream, then fall back to the runtime "using model" log marker emitted by active planner/executor/reviewer sessions. Merger output uses the validator/reviewer lane provider because merge-fix/review flows share that model family in the UI.
*/
return getExplicitModelForRole(task, role) ?? getRuntimeModelForRole(entries, role);
}
function TaskChatAgentIcon({ label, modelInfo, role }: { label: string; modelInfo: TaskChatModelInfo | null; role: AgentLogRole }) {
if (modelInfo?.provider) {
const title = modelInfo.modelId ? `${label}: ${modelInfo.provider}/${modelInfo.modelId}` : `${label}: ${modelInfo.provider}`;
return (
<span className="task-chat-provider-icon" title={title} aria-label={title}>
<ProviderIcon provider={modelInfo.provider} size="md" />
</span>
);
}
const avatarAgent = {
id: role ?? "agent",
name: label,
icon: getRoleIcon(role),
};
return <AgentAvatar agent={avatarAgent} className="task-chat-avatar" />;
}
function getEntryKey(entry: AgentLogEntry, index: number): string {
return [entry.taskId, entry.timestamp, entry.agent ?? "agent", entry.type, index].join(":");
}
@@ -810,18 +877,14 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
return <TaskChatUserMessage key={`user-${item.message.id}-${itemIndex}`} message={item.message} />;
}
const avatarAgent = {
id: item.role ?? "agent",
name: item.label,
icon: getRoleIcon(item.role),
};
const segments = segmentGroupEntries(item.entries);
const latestEntryTimestamp = item.entries[item.entries.length - 1]?.timestamp ?? "";
const relativeTime = formatRelativeTimeAgo(latestEntryTimestamp);
const modelInfo = getModelForRole(task, item.role, item.entries);
return (
<section className="task-chat-group" key={`${item.role ?? "agent"}-${itemIndex}`} aria-label={t("taskChat.agentMessages", "{{label}} messages", { label: item.label })}>
<header className="task-chat-group-header">
<AgentAvatar agent={avatarAgent} className="task-chat-avatar" />
<TaskChatAgentIcon label={item.label} modelInfo={modelInfo} role={item.role} />
<div>
<div className="task-chat-role-label">{item.label}</div>
<div className="task-chat-group-meta">

View File

@@ -37,19 +37,24 @@ The override MUST out-specify the base `.modal-overlay` (which sets a dimmed bac
/*
FNXC:Terminal 2026-06-23-04:30:
The terminal must NEVER dim or blur the page behind it, in ANY mode. The base .modal-overlay applies a dimmed background + blur; the docked/floating overrides killed it, but a terminal that is NEITHER (the mobile/default sheet) still fell back to the dim. This base terminal-overlay rule removes the dim+blur for every terminal state.
FNXC:Terminal 2026-06-23-21:28:
Theme-level modal backdrop rules can load after component CSS and reapply dim/blur, especially in glass themes. Use a higher-specificity terminal overlay selector and clear both standard and WebKit backdrop filters so docked and modal terminal surfaces never darken the app behind them.
*/
.modal-overlay.terminal-modal-overlay {
.modal-overlay.terminal-modal-overlay.terminal-modal-overlay {
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
.modal-overlay.terminal-modal-overlay--docked,
.modal-overlay.terminal-modal-overlay--floating {
.modal-overlay.terminal-modal-overlay--docked.terminal-modal-overlay--docked,
.modal-overlay.terminal-modal-overlay--floating.terminal-modal-overlay--floating {
align-items: stretch;
justify-content: flex-end;
padding: 0;
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
pointer-events: none;
}

View File

@@ -315,11 +315,12 @@ describe("GitManagerModal", () => {
it("keeps the sidebar-launched Git Manager overlay transparent and click-through like Files", () => {
const css = loadAllAppCss();
const overlayRule = css.match(/\.modal-overlay\.git-manager-modal-overlay\s*\{([^}]*)\}/)?.[1] ?? "";
const overlayRule = css.match(/\.modal-overlay\.git-manager-modal-overlay\.git-manager-modal-overlay\s*\{([^}]*)\}/)?.[1] ?? "";
const panelRule = css.match(/\.modal\.gm-modal\s*\{([^}]*)\}/)?.[1] ?? "";
expect(overlayRule).toContain("background: transparent");
expect(overlayRule).toContain("backdrop-filter: none");
expect(overlayRule).toContain("-webkit-backdrop-filter: none");
expect(overlayRule).toContain("pointer-events: none");
expect(panelRule).toContain("pointer-events: auto");
});

View File

@@ -32,7 +32,11 @@ describe("Header CSS", () => {
});
it("compacts the workflow portal in the mobile top header", () => {
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot\s*\{[^}]*max-width:\s*min\(42vw,\s*calc\(var\(--space-2xl\) \* 4\)\);/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot\s*\{[^}]*flex:\s*1 1 auto;[^}]*justify-content:\s*center;[^}]*max-width:\s*none;/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-actions\s*\{[^}]*flex:\s*0 0 auto;[^}]*align-items:\s*center;[^}]*gap:\s*var\(--space-sm\);/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot \.board-workflow-toolbar,\s*\n\s*\.header-workflow-slot \.list-workflow-control\s*\{[^}]*height:\s*32px;[^}]*align-items:\s*center;/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot \.workflow-switcher\s*\{[^}]*width:\s*clamp\(calc\(var\(--space-2xl\) \* 3\.25\),\s*36vw,\s*calc\(var\(--space-2xl\) \* 4\)\);[^}]*height:\s*32px;[^}]*max-height:\s*32px;[^}]*align-items:\s*center;/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot \.workflow-switcher-trigger\s*\{[^}]*appearance:\s*none;[^}]*height:\s*32px;[^}]*min-height:\s*32px;[^}]*max-height:\s*32px;[^}]*line-height:\s*1;[^}]*overflow:\s*hidden;/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot \.workflow-switcher-label\s*\{[^}]*display:\s*none;/);
expect(css).toMatch(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.header-workflow-slot \.workflow-switcher-counts\s*\{[^}]*display:\s*none;/);
});

View File

@@ -1597,11 +1597,16 @@ describe("ListView", () => {
it("keeps the primary list action cluster on one physical row when the pane narrows", () => {
const css = readFileSync("app/components/ListView.css", "utf8");
const actionClusterRule = css.match(/\.list-action-cluster,\s*\n\.list-sidebar-controls__actions\s*\{[^}]*\}/)?.[0] ?? "";
const toolbarRule = css.match(/\.list-sidebar-controls__toolbar\s*\{[^}]*\}/)?.[0] ?? "";
const mobileToolbarRule = css.match(/@media\s*\(max-width:\s*768px\)[\s\S]*?\.list-toolbar\s*\{[^}]*padding:\s*var\(--space-sm\) var\(--space-md\);[^}]*\}/)?.[0] ?? "";
expect(actionClusterRule).toContain("flex-wrap: nowrap");
expect(actionClusterRule).toContain("justify-content: center");
expect(actionClusterRule).toContain("inline-size: max-content");
expect(actionClusterRule).toContain("min-width: max-content");
expect(actionClusterRule).toContain("overflow-x: auto");
expect(toolbarRule).toContain("justify-content: center");
expect(mobileToolbarRule).toContain("justify-content: center");
});
it("keeps Bulk Edit, View, and + New Task together in the mobile toolbar controls", () => {

View File

@@ -425,6 +425,48 @@ describe("TaskChatTab", () => {
expect(screen.getByText("legacy output")).toBeTruthy();
});
it("renders provider icons for task chat roles from task model overrides", () => {
mockLogs([
makeEntry({ agent: "triage", text: "planning output" }),
makeEntry({ agent: "executor", text: "executor output" }),
makeEntry({ agent: "reviewer", text: "reviewer output" }),
makeEntry({ agent: "merger", text: "merger output" }),
]);
render(
<TaskChatTab
task={makeTask({
planningModelProvider: "google",
planningModelId: "gemini-pro",
modelProvider: "openai",
modelId: "gpt-4o",
validatorModelProvider: "anthropic",
validatorModelId: "claude-sonnet-4-5",
})}
active
addToast={vi.fn()}
/>,
);
expect(document.querySelector(".task-chat-provider-icon [data-provider='google']")).toBeTruthy();
expect(document.querySelector(".task-chat-provider-icon [data-provider='openai']")).toBeTruthy();
expect(document.querySelectorAll(".task-chat-provider-icon [data-provider='anthropic']")).toHaveLength(2);
});
it("renders provider icons for task chat roles from runtime model markers", () => {
mockLogs([
makeEntry({ agent: "triage", text: "Triage using model: google/gemini-pro" }),
makeEntry({ agent: "executor", text: "Executor using model: openai/gpt-4o" }),
makeEntry({ agent: "reviewer", text: "Reviewer using model: anthropic/claude-sonnet-4-5" }),
]);
render(<TaskChatTab task={makeTask()} active addToast={vi.fn()} />);
expect(document.querySelector(".task-chat-provider-icon [data-provider='google']")).toBeTruthy();
expect(document.querySelector(".task-chat-provider-icon [data-provider='openai']")).toBeTruthy();
expect(document.querySelector(".task-chat-provider-icon [data-provider='anthropic']")).toBeTruthy();
});
it("groups consecutive entries by agent role", () => {
mockLogs([
makeEntry({ agent: "executor", text: "first" }),

View File

@@ -477,6 +477,33 @@ Shadcn-family themes should read as seamless app shells: header and modal-title
border-bottom-color: transparent;
}
/*
FNXC:DashboardTheming 2026-06-23-00:07:
All shadcn-family themes should use one UI font family across app chrome, forms, popovers, and buttons. Native controls do not reliably inherit the document font in every browser, so pin shadcn UI controls to --font-primary while preserving --font-mono for code-oriented text.
*/
[data-color-theme^="shadcn"],
[data-color-theme^="shadcn"] body,
[data-color-theme^="shadcn"] button,
[data-color-theme^="shadcn"] input,
[data-color-theme^="shadcn"] select,
[data-color-theme^="shadcn"] textarea,
[data-color-theme^="shadcn"] optgroup,
[data-color-theme^="shadcn"] .btn,
[data-color-theme^="shadcn"] .input,
[data-color-theme^="shadcn"] .select,
[data-color-theme^="shadcn"] .modal,
[data-color-theme^="shadcn"] .card {
font-family: var(--font-primary);
}
[data-color-theme^="shadcn"] code,
[data-color-theme^="shadcn"] pre,
[data-color-theme^="shadcn"] kbd,
[data-color-theme^="shadcn"] samp,
[data-color-theme^="shadcn"] .font-mono {
font-family: var(--font-mono);
}
/* GRAPHITE - Dark graphite blacks */
[data-color-theme="graphite"] {
--surface-hover: color-mix(in srgb, var(--surface) 90%, var(--text) 10%);
@@ -5619,8 +5646,13 @@ body[data-color-theme="terminal"][data-theme="light"]::before {
}
[data-color-theme="glass"] .modal-overlay {
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
/*
FNXC:ModalChrome 2026-06-23-21:36:
Even glass-style themes should keep modal overlays non-dimming/non-blurring; modal panels provide depth with shadows and their own translucent surfaces.
*/
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* HORIZON - Warm sunset-inspired palette */

View File

@@ -639,11 +639,10 @@ html .column.drag-over * {
transition: none !important;
}
[data-theme="light"] .modal-overlay,
[data-theme="light"] .agent-detail-overlay,
[data-theme="light"] .agent-dialog-overlay,
[data-theme="light"] .chat-new-dialog-backdrop {
background: color-mix(in srgb, var(--text) 50%, transparent);
background: transparent;
}
[data-theme="light"] .modal-header {
@@ -1205,12 +1204,17 @@ body {
/* === Modals === */
/*
FNXC:ModalChrome 2026-06-23-21:36:
Modal and dock pop-out surfaces should not darken or blur the app behind them. Keep the overlay for positioning, stacking, and click/escape dismissal, but make it visually transparent; panel depth comes from each `.modal` box-shadow instead of a dimmed backdrop. This shared contract covers Git Manager, Terminal, Dev Server, Secrets, and future sidebar/tool modals without per-modal overrides.
*/
.modal-overlay {
display: none;
position: fixed;
inset: 0;
background: color-mix(in srgb, var(--text) 60%, transparent);
backdrop-filter: blur(4px);
background: transparent;
backdrop-filter: none;
-webkit-backdrop-filter: none;
/* Must remain above sticky top banners (e.g. onboarding/session resume cards). */
z-index: 100;
justify-content: center;