fix(canvas): 创作工作区 - 收紧界面密度并改进 Agent 镂空标签

This commit is contained in:
ddcat666
2026-09-24 10:07:05 +08:00
parent 3ef40e1d21
commit 32a2e44040
9 changed files with 158 additions and 73 deletions

View File

@@ -4,7 +4,7 @@ import { Tooltip } from "@/components/ui/base/tooltip";
import { useCallback, useEffect, useMemo, useRef, useState, type ClipboardEvent as ReactClipboardEvent, type CSSProperties, type KeyboardEvent as ReactKeyboardEvent, type PointerEvent as ReactPointerEvent, type ReactNode } from "react";
import { motion, useReducedMotion } from "motion/react";
import { ArrowUp, AtSign, CheckCircle2, ChevronDown, ChevronUp, CircleAlert, CircleDot, Eye, HelpCircle, ImagePlus, ListChecks, LoaderCircle, Pencil, Plus, RotateCcw, Sparkles, Square, X, XCircle } from "lucide-react";
import { ArrowLeft, ArrowUp, AtSign, Bookmark, CheckCircle2, ChevronDown, ChevronUp, CircleAlert, CircleDot, Clapperboard, Eye, HelpCircle, ImagePlus, Layers3, ListChecks, LoaderCircle, Palette, Pencil, Plus, RotateCcw, Shapes, Share2, ShoppingBag, Sparkles, Square, X, XCircle } from "lucide-react";
import { canvasThemes } from "@/lib/canvas-theme";
import { AIMessageMarkdown } from "@/components/ai/ai-message-markdown";
@@ -595,13 +595,13 @@ export type AgentSceneBucket = {
};
/** 场景分类:与 presets.json 的 scene 字段、技能的 tag 字段共用同一套 key。 */
export const AGENT_SCENE_DEFS: Array<{ key: string; label: string }> = [
{ key: "frequent", label: "我的常用" },
{ key: "drama", label: "短剧故事" },
{ key: "ecommerce", label: "广告电商" },
{ key: "creative", label: "视觉创意" },
{ key: "social", label: "传播社媒" },
{ key: "others", label: "其他" },
export const AGENT_SCENE_DEFS: Array<{ key: string; label: string; icon: typeof Sparkles }> = [
{ key: "frequent", label: "我的常用", icon: Bookmark },
{ key: "drama", label: "短剧故事", icon: Clapperboard },
{ key: "ecommerce", label: "广告电商", icon: ShoppingBag },
{ key: "creative", label: "视觉创意", icon: Palette },
{ key: "social", label: "传播社媒", icon: Share2 },
{ key: "others", label: "其他", icon: Shapes },
];
export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = false, onPick, onPickSkill }: {
@@ -614,7 +614,7 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
}) {
// 始终只占一排:默认显示场景分类,点某个场景后在同一排内就地切换内容。
const [activeKey, setActiveKey] = useState<string | null>(null);
const capsuleClass = "agent-scene-capsule shrink-0 rounded-lg px-3 py-1.5 text-left text-xs focus-visible:outline focus-visible:outline-2";
const capsuleClass = "agent-scene-capsule shrink-0";
const stop = {
onMouseDown: (event: { stopPropagation(): void }) => event.stopPropagation(),
onPointerDown: (event: { stopPropagation(): void }) => event.stopPropagation(),
@@ -623,12 +623,12 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
const active = activeKey ? visible.find((bucket) => bucket.key === activeKey) || null : null;
if (!visible.length) return null;
return (
<div className="agent-scene-capsules mx-3 mb-2 overflow-hidden rounded-xl" style={{ color: theme.node.text }}>
<div className="flex items-start gap-2 px-3 pt-2.5">
<Sparkles className="mt-[1px] size-3.5 shrink-0" style={{ color: theme.accent.primary }} />
<span className="min-w-0 flex-1 text-xs font-semibold leading-5">{active ? active.label : "技能组合推荐"}</span>
<div className="agent-scene-capsules mx-3 mb-2 min-w-0" style={{ color: theme.node.text }}>
<div className="agent-scene-capsules-heading">
<Sparkles aria-hidden="true" />
<span>{active ? active.label : "技能组合推荐"}</span>
</div>
<div className="agent-scene-capsules-scroll thin-scrollbar flex gap-2 overflow-x-auto px-3 pb-1 pt-2">
<div className="agent-scene-capsules-scroll thin-scrollbar flex gap-2 overflow-x-auto px-1 py-2">
{active ? (
<>
<button
@@ -636,14 +636,15 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
disabled={disabled}
title="返回全部场景"
className={capsuleClass}
data-scene="back"
{...stop}
onClick={(event) => {
event.stopPropagation();
setActiveKey(null);
}}
>
<span className="block whitespace-nowrap font-medium">← 全部场景</span>
<span className="mt-0.5 block whitespace-nowrap text-[10px] leading-4 opacity-60">返回</span>
<ArrowLeft aria-hidden="true" />
<span>全部场景</span>
</button>
{active.presets.map((preset) => {
const missing = preset.skillIds.filter((id) => !installedIds.has(id)).length;
@@ -654,16 +655,16 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
disabled={disabled}
title={preset.rationale}
className={capsuleClass}
data-scene={active.key}
{...stop}
onClick={(event) => {
event.stopPropagation();
onPick(preset);
}}
>
<span className="block whitespace-nowrap font-medium">{preset.name}</span>
<span className="mt-0.5 block whitespace-nowrap text-[10px] leading-4 opacity-60">
{missing > 0 ? `${preset.skillIds.length} 个技能 · ${missing} 个待装` : `${preset.skillIds.length} 个技能`}
</span>
<Layers3 aria-hidden="true" />
<span className="agent-scene-capsule-label">{preset.name}</span>
<span className="agent-scene-capsule-meta">{missing > 0 ? `${preset.skillIds.length} 技能 · ${missing} 待装` : `${preset.skillIds.length} 技能`}</span>
</button>
);
})}
@@ -676,14 +677,16 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
disabled={disabled}
title={skill.description}
className={capsuleClass}
data-scene={active.key}
{...stop}
onClick={(event) => {
event.stopPropagation();
onPickSkill(skill);
}}
>
<span className="block max-w-[10rem] truncate font-medium">{skill.skillName}</span>
<span className="mt-0.5 block whitespace-nowrap text-[10px] leading-4 opacity-60">{owned}</span>
<Sparkles aria-hidden="true" />
<span className="agent-scene-capsule-label">{skill.skillName}</span>
<span className="agent-scene-capsule-meta">{owned}</span>
</button>
);
})}
@@ -691,6 +694,7 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
) : (
visible.map((bucket) => {
const count = bucket.presets.length + bucket.skills.length;
const Icon = AGENT_SCENE_DEFS.find((definition) => definition.key === bucket.key)?.icon || Shapes;
return (
<button
key={bucket.key}
@@ -698,20 +702,22 @@ export function AgentSceneCapsules({ buckets, installedIds, theme, disabled = fa
disabled={disabled}
title={`查看「${bucket.label}」下的技能组合`}
className={capsuleClass}
data-scene={bucket.key}
{...stop}
onClick={(event) => {
event.stopPropagation();
setActiveKey(bucket.key);
}}
>
<span className="block whitespace-nowrap font-medium">{bucket.label}</span>
<span className="mt-0.5 block whitespace-nowrap text-[10px] leading-4 opacity-60">{count} 项</span>
<Icon aria-hidden="true" />
<span className="agent-scene-capsule-label">{bucket.label}</span>
<span className="agent-scene-capsule-meta">{count} 项</span>
</button>
);
})
)}
</div>
<div className="px-3 pb-2 text-[10px] opacity-50">选择只对当前会话生效;未装的技能会添加到我的技能库。具体用哪张卡由 Agent 按任务检索。</div>
<p className="agent-scene-capsules-note">仅本会话生效 · 缺失技能将加入技能库 · Agent 按任务调用</p>
</div>
);
}

View File

@@ -1,7 +1,7 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type Dispatch, type SetStateAction } from "react";
import { Button, Dropdown, Input } from "antd";
import { AnimatePresence, motion, useReducedMotion } from "motion/react";
import { ArrowLeft, Check, ChevronRight, CircleDot, Clock3, Download, History, LoaderCircle, MessageSquarePlus, MoveDiagonal2, Settings2, ShieldCheck, Trash2, Sparkles, X } from "lucide-react";
import { ArrowLeft, Check, ChevronRight, CircleDot, Clock3, Download, History, LoaderCircle, MessageSquarePlus, MoveDiagonal2, RotateCcw, Settings2, ShieldCheck, Trash2, Sparkles, X } from "lucide-react";
import { saveAs } from "file-saver";
import { buildAgentDebugExport } from "@/lib/canvas/agent-debug-export";
import { markdownPlainText } from "@/lib/markdown-plain-text";
@@ -840,6 +840,7 @@ export function CanvasCloudAgentPanel({ canvasId, domainProjectId, nodeCount, re
})().catch((cause) => setMessages((current) => appendAgentError(current, `export-${Date.now()}`, cause, "导出失败"))).finally(() => setExporting(false));
}}
onSettings={() => setView("settings")}
onResetLayout={panelLayout.reset}
onCollapse={onCollapse}
/>
{run && connectionStatus !== "connected" ? (
@@ -978,7 +979,7 @@ function AgentLauncher({ theme, statusColor, approvalPending, reducedMotion, onO
);
}
function AgentHeader({ theme, hasMessages, statusLabel, statusColor, nodeCount, onNew, onHistory, onSettings, onCollapse, onExport, exporting }: { theme: CanvasTheme; hasMessages: boolean; statusLabel: string; statusColor: string; nodeCount: number; onNew: () => void; onHistory: () => void; onSettings: () => void; onCollapse: () => void; onExport: () => void; exporting: boolean }) {
function AgentHeader({ theme, hasMessages, statusLabel, statusColor, nodeCount, onNew, onHistory, onSettings, onResetLayout, onCollapse, onExport, exporting }: { theme: CanvasTheme; hasMessages: boolean; statusLabel: string; statusColor: string; nodeCount: number; onNew: () => void; onHistory: () => void; onSettings: () => void; onResetLayout: () => void; onCollapse: () => void; onExport: () => void; exporting: boolean }) {
const appearance = useAppearanceStore((state) => state.appearance.canvas) || DEFAULT_CANVAS_APPEARANCE;
return (
<header data-agent-drag-handle className="agent-panel-header flex shrink-0 items-center gap-3">
@@ -993,6 +994,7 @@ function AgentHeader({ theme, hasMessages, statusLabel, statusColor, nodeCount,
<div className="agent-panel-context">{appearance.agentName} · 当前画布 {nodeCount} 个节点</div>
</div>
<div className="agent-header-actions flex items-center gap-0.5" style={{ color: theme.node.muted }}>
<Button type="text" shape="circle" icon={<RotateCcw className="size-4" />} onClick={onResetLayout} aria-label="恢复 Agent 紧凑窗口" title="恢复默认窗口大小和位置" className="hidden sm:inline-flex" />
<Button type="text" shape="circle" icon={<Download className="size-4" />} loading={exporting} onClick={onExport} aria-label="导出 Agent 调试记录" title="导出对话、工具参数、审批和错误(分享前请检查隐私)" />
<Button type="text" shape="circle" icon={<MessageSquarePlus className="size-4" />} onClick={onNew} aria-label="新建对话" title="新建对话" />
<Button type="text" shape="circle" icon={<History className="size-4" />} onClick={onHistory} aria-label="历史对话" title="历史对话" />

View File

@@ -30,8 +30,8 @@
}
.agent-panel-header {
min-height: 80px;
padding: var(--space-4) var(--space-5) var(--space-3) var(--space-6);
min-height: 68px;
padding: var(--space-3) var(--space-4) var(--space-2) var(--space-5);
}
.agent-panel-title {
@@ -55,8 +55,8 @@
}
.agent-conversation {
padding: var(--space-4) var(--space-6) var(--space-6);
scroll-padding-block: var(--space-6);
padding: var(--space-3) var(--space-5) var(--space-5);
scroll-padding-block: var(--space-5);
}
.agent-conversation-messages > * + * {
@@ -193,11 +193,11 @@
}
.agent-composer-wrap {
padding: var(--space-2) var(--space-4) var(--space-4);
padding: var(--space-2) var(--space-3) var(--space-3);
}
.agent-composer-surface {
padding: var(--space-4) var(--space-4) var(--space-3);
padding: var(--space-3) var(--space-3) var(--space-2-half);
border-radius: var(--agent-radius-surface, var(--radius-xl));
background: var(--agent-surface-raised, var(--background));
}
@@ -216,27 +216,84 @@
background: var(--agent-surface-raised, var(--background));
}
/* 场景起步胶囊:选择作用于会话,未安装的技能会加入用户技能库。 */
.agent-scene-capsules {
border: 0;
background: var(--agent-surface-soft, color-mix(in srgb, var(--foreground) 4%, transparent));
/* 场景起步标签:透明底、彩色细描边,不叠加卡片表面。 */
.agent-scene-capsules-heading {
display: flex;
align-items: center;
gap: var(--space-2);
padding-inline: var(--space-1);
color: var(--agent-ink);
font-size: var(--fs-caption);
font-weight: 600;
}
.agent-scene-capsules-heading svg {
width: var(--space-4);
height: var(--space-4);
color: var(--palette-node-skill);
}
.agent-scene-capsule {
border: 1px solid color-mix(in srgb, var(--foreground) 10%, transparent);
background: var(--agent-surface-raised, var(--background));
--scene-color: var(--palette-node-config);
display: inline-flex;
min-height: 36px;
max-width: 100%;
align-items: center;
gap: var(--space-2);
padding: var(--space-1-half) var(--space-3);
border: 1px solid color-mix(in srgb, var(--scene-color) 58%, var(--agent-ink));
border-radius: var(--r-full);
background: transparent;
color: color-mix(in srgb, var(--scene-color) 45%, var(--agent-ink));
font-size: var(--fs-caption);
line-height: 1.2;
white-space: nowrap;
cursor: pointer;
transition: border-color 140ms ease, background 140ms ease, transform 140ms ease;
transition: border-color 160ms ease, color 160ms ease, opacity 160ms ease;
}
.agent-scene-capsule[data-scene="frequent"] { --scene-color: var(--palette-node-skill); }
.agent-scene-capsule[data-scene="drama"] { --scene-color: var(--palette-node-script); }
.agent-scene-capsule[data-scene="ecommerce"] { --scene-color: var(--palette-node-image); }
.agent-scene-capsule[data-scene="creative"] { --scene-color: var(--palette-node-frame); }
.agent-scene-capsule[data-scene="social"] { --scene-color: var(--palette-node-audio); }
.agent-scene-capsule[data-scene="back"] { --scene-color: var(--agent-ink); }
.agent-scene-capsule svg {
width: var(--space-4);
height: var(--space-4);
flex: 0 0 auto;
stroke-width: 1.8;
color: color-mix(in srgb, var(--scene-color) 75%, var(--agent-ink));
}
.agent-scene-capsule-label {
min-width: 0;
max-width: 10rem;
overflow: hidden;
text-overflow: ellipsis;
font-weight: 600;
}
.agent-scene-capsule-meta {
flex: 0 0 auto;
color: var(--agent-caption);
font-size: var(--fs-label);
font-variant-numeric: tabular-nums;
}
.agent-scene-capsule:hover:not(:disabled) {
border-color: color-mix(in srgb, var(--scene-color) 82%, var(--agent-ink));
color: color-mix(in srgb, var(--scene-color) 70%, var(--agent-ink));
}
.agent-scene-capsule:hover:not(:disabled),
.agent-scene-capsule:focus-visible {
border-color: color-mix(in srgb, var(--agent-accent, var(--foreground)) 45%, transparent);
background: var(--agent-surface-soft, color-mix(in srgb, var(--foreground) 6%, transparent));
outline: 2px solid var(--agent-focus-ring);
outline-offset: 2px;
}
.agent-scene-capsule:active:not(:disabled) {
transform: translateY(1px);
opacity: 0.65;
}
.agent-scene-capsule:disabled {
@@ -244,6 +301,14 @@
opacity: 0.55;
}
.agent-scene-capsules-note {
margin: 0;
padding-inline: var(--space-1);
color: var(--agent-caption);
font-size: var(--fs-label);
line-height: 1.5;
}
.agent-scene-capsules-scroll {
scrollbar-width: thin;
}
@@ -261,9 +326,6 @@
.agent-scene-capsule {
transition: none;
}
.agent-scene-capsule:active:not(:disabled) {
transform: none;
}
}
@container (max-width: 400px) {

View File

@@ -1,5 +1,5 @@
import { useEffect, useRef, useState, type PointerEvent, type KeyboardEvent } from "react";
import { AGENT_PANEL_LAYOUT_KEY, changeAgentPanelLayout, clampAgentPanelLayout, restoreAgentPanelLayout, type AgentPanelGesture, type AgentPanelLayout } from "@/lib/canvas/agent-panel-layout";
import { AGENT_PANEL_LAYOUT_KEY, changeAgentPanelLayout, clampAgentPanelLayout, defaultAgentPanelLayout, restoreAgentPanelLayout, type AgentPanelGesture, type AgentPanelLayout } from "@/lib/canvas/agent-panel-layout";
const viewport = () => ({ width: window.innerWidth, height: window.innerHeight });
@@ -69,6 +69,7 @@ export function useAgentPanelLayout() {
return {
style: compact ? { left: 0, top: 8, width: "100%", height: "calc(100dvh - 8px)" } : layout,
reset: () => setLayout(defaultAgentPanelLayout(viewport())),
onResizeKeyDown,
pointerHandlers: { onPointerDown, onPointerMove, onPointerUp, onPointerCancel: onPointerUp, onLostPointerCapture: () => { gestureRef.current = null; } },
};

View File

@@ -58,10 +58,9 @@ type DockMetrics = {
distance: number;
};
// magnification 收敛:base 略增以适配圆角方块,magnified 幅度从 +14 降到 +8,
// 避免 dock 整体跳动,同时保留 Aceternity 接近放大身份。
// 桌面 dock 收紧常态与悬浮尺寸;触屏尺寸单独保留以保证点击目标。
const DOCK_METRICS: Record<NonNullable<FloatingDockProps["size"]>, DockMetrics> = {
default: { base: 30, magnified: 38, icon: 15, iconMagnified: 18, distance: 100 },
default: { base: 28, magnified: 34, icon: 14, iconMagnified: 16, distance: 100 },
compact: { base: 26, magnified: 32, icon: 13, iconMagnified: 16, distance: 84 },
};
@@ -136,7 +135,7 @@ export const FloatingDock = forwardRef<HTMLDivElement, FloatingDockProps>(functi
: "h-9 gap-0.5 px-0.5 pb-0.5"
: size === "compact"
? "h-8 gap-0.5 rounded-[var(--r-lg)] px-1 pb-1"
: "h-10 gap-0.5 rounded-[var(--dock-radius)] px-1.5 pb-1",
: "h-9 gap-0.5 rounded-[var(--dock-radius)] px-1.5 pb-0.5",
className,
)}
style={style}

View File

@@ -20,7 +20,7 @@ export function clampAgentPanelLayout(layout: AgentPanelLayout, viewport: AgentP
}
export function restoreAgentPanelLayout(raw: string | null, viewport: AgentPanelViewport): AgentPanelLayout {
const fallback = { width: 448, height: 720, left: viewport.width - 464, top: viewport.height - 732 };
const fallback = defaultAgentPanelLayout(viewport);
if (!raw) return clampAgentPanelLayout(fallback, viewport);
try {
const parsed: unknown = JSON.parse(raw);
@@ -33,6 +33,12 @@ export function restoreAgentPanelLayout(raw: string | null, viewport: AgentPanel
return clampAgentPanelLayout(fallback, viewport);
}
export function defaultAgentPanelLayout(viewport: AgentPanelViewport): AgentPanelLayout {
const width = 420;
const height = 640;
return clampAgentPanelLayout({ width, height, left: viewport.width - width - MARGIN, top: viewport.height - height - MARGIN }, viewport);
}
export function changeAgentPanelLayout(start: AgentPanelLayout, gesture: AgentPanelGesture, dx: number, dy: number, viewport: AgentPanelViewport): AgentPanelLayout {
if (gesture === "move") return clampAgentPanelLayout({ ...start, left: start.left + dx, top: start.top + dy }, viewport);
const right = start.left + start.width;

View File

@@ -249,7 +249,7 @@
--canvas-inset-x: 16px; /* 浮层水平安全区(dock inset、顶栏 px)*/
--canvas-inset-y: 16px; /* 浮层垂直安全区(dock 底部)*/
--canvas-topbar-h: 64px; /* 顶部栏高度 */
--canvas-topbar-offset: 72px; /* 顶栏下方浮层顶部偏移(topbar-h + 8px 间距)*/
--canvas-topbar-offset: calc(var(--canvas-topbar-h) + 8px); /* 顶栏下方浮层顶部偏移 */
--canvas-dock-popover-offset: 50px; /* dock 弹出面板底部偏移 */
/* --- 7. 动效(对应 #99 动效 token)--- */
@@ -1691,9 +1691,9 @@
--workspace-accent-soft: rgba(17, 17, 17, 0.06);
--workspace-nav-active-bg: rgba(17, 17, 17, 0.075);
--workspace-nav-active-hover-bg: rgba(17, 17, 17, 0.11);
--workspace-sidebar-width: 312px;
--workspace-sidebar-nav-width: 312px;
--workspace-sidebar-collapsed-width: 86px;
--workspace-sidebar-width: 200px;
--workspace-sidebar-nav-width: 200px;
--workspace-sidebar-collapsed-width: 64px;
--workspace-shell-gutter: 0px;
--workspace-card-gap: 0px;
--workspace-topbar-height: 56px;
@@ -1719,10 +1719,10 @@
/* 画布编辑器保持独立的全屏工作区,但沿用用户端的安全区和控件密度。 */
.app-user-workspace.app-spatial-workspace {
--canvas-topbar-h: 64px;
--canvas-inset-x: 16px;
--canvas-inset-y: 16px;
--canvas-dock-popover-offset: 50px;
--canvas-topbar-h: 56px;
--canvas-inset-x: 12px;
--canvas-inset-y: 12px;
--canvas-dock-popover-offset: 46px;
}
/* 画布编辑器和管理后台不继承用户端的圆角卡片外壳。 */
@@ -15180,12 +15180,12 @@ html:not(.dark) .creation-home .creation-user-message-copy-wrap { box-shadow: 0
/* ===== 2026-09 user workspace alignment =====
这一组规则只作用于真实用户端壳层和首页,后台与画布编辑器保持原有职责和交互。
几何基准:侧栏 216px、顶部 64px、导航行 44px、首页内容最大宽度 1180px。 */
几何基准:侧栏 200px、顶部 56px、导航行 40px、首页内容最大宽度 1180px。 */
.app-user-workspace {
--workspace-reference-sidebar: 216px;
--workspace-reference-collapsed: 70px;
--workspace-reference-topbar: 64px;
--workspace-reference-row: 44px;
--workspace-reference-sidebar: 200px;
--workspace-reference-collapsed: 64px;
--workspace-reference-topbar: 56px;
--workspace-reference-row: 40px;
--workspace-reference-page-bg: #ffffff;
background: var(--workspace-reference-page-bg);
}
@@ -17844,8 +17844,8 @@ html:not(.dark) .creation-home .creation-user-message-copy-wrap { box-shadow: 0
.app-user-workspace .app-workspace-sidebar-scroll-area { padding: 22px 12px 12px !important; gap: 24px !important; }
.app-user-workspace .app-workspace-sidebar-footer { padding: 12px 12px 14px !important; border-top: 0 !important; }
.app-user-workspace .app-workspace-nav-link {
min-height: 44px !important;
padding: 4px 10px !important;
min-height: var(--workspace-reference-row) !important;
padding: 3px 10px !important;
border-radius: 13px !important;
color: var(--user-ink-muted) !important;
font-size: 13px !important;
@@ -17860,13 +17860,13 @@ html:not(.dark) .creation-home .creation-user-message-copy-wrap { box-shadow: 0
.app-user-workspace .app-workspace-nav-link:focus-visible:not(.is-active) { background: var(--user-surface-hover) !important; color: var(--user-ink) !important; }
.app-user-workspace .app-workspace-nav-group-toggle { color: var(--user-ink-soft) !important; }
.app-user-workspace .app-workspace-sidebar.is-collapsed .app-workspace-sidebar-scroll-area { padding-inline: 8px !important; }
.app-user-workspace .app-workspace-sidebar.is-collapsed .app-workspace-nav-link { width: 54px !important; }
.app-user-workspace .app-workspace-sidebar.is-collapsed .app-workspace-nav-link { width: 48px !important; }
.app-user-workspace .app-workspace-topbar {
min-height: 64px !important;
height: 64px !important;
min-height: var(--workspace-reference-topbar) !important;
height: var(--workspace-reference-topbar) !important;
overflow: visible;
padding-inline: 24px !important;
padding-inline: 20px !important;
border-bottom: 0 !important;
background: color-mix(in srgb, var(--user-page-bg) 92%, transparent) !important;
color: var(--user-ink) !important;

View File

@@ -1,5 +1,5 @@
import { describe, expect, it } from "bun:test";
import { changeAgentPanelLayout, clampAgentPanelLayout, restoreAgentPanelLayout } from "@/lib/canvas/agent-panel-layout";
import { changeAgentPanelLayout, clampAgentPanelLayout, defaultAgentPanelLayout, restoreAgentPanelLayout } from "@/lib/canvas/agent-panel-layout";
import { agentErrorPresentation, agentSubmissionErrorTitle } from "@/lib/canvas/agent-error-presentation";
import { ApiError } from "@/services/api/request";
@@ -15,6 +15,10 @@ describe("Agent window layout", () => {
expect(layout.height).toBe(626);
expect(layout.top).toBe(12);
});
it("offers a compact reset without altering a saved window until requested", () => {
expect(restoreAgentPanelLayout(JSON.stringify(start), viewport)).toEqual(start);
expect(defaultAgentPanelLayout(viewport)).toEqual({ left: 848, top: 148, width: 420, height: 640 });
});
it("keeps the bottom/right anchor when resizing from top/left", () => {
const layout = changeAgentPanelLayout(start, "northwest", -120, 60, viewport);
expect(layout).toEqual({ left: 696, top: 128, width: 568, height: 660 });