diff --git a/README.md b/README.md index 6851d50a..70855fce 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Makelore 是一个面向软件、视觉创作、智能机器人与个人云智 Agents 侧栏的「渠道」按每个微信一张卡展示备注、当前智能体与使用状态;一个微信同时只绑定一个智能体,多个微信可以使用同一智能体。常用操作是“更换智能体”,在同一个选择窗口中确认;暂停自动回复和解除微信绑定放在“更多操作”。暂停停止当前任务并保留登录、绑定和历史;更换保留登录与原启停状态;解除微信绑定是完整操作,停止任务、清除目标并退出微信登录,历史保留,再使用需扫码。掉线显示重新扫码入口,暂停显示继续回复,未知或未同步状态显示正在确认,不宣称可用。添加流程为选择已开始使用的智能体、扫码、自动完成绑定;只有这次用户发起的绑定在扫码确认、目标正确、启用成功且最终状态回读可用后才显示完成,不因页面打开、旧记录或验证码提交自动启用。无可用智能体时说明创建和“开始使用”的前提,加载失败不冒充空态,已有微信仍可管理或解绑。Main 持有凭据与原操作恢复;解除需要配套 Yuxi 用户级 `/channel-accounts/{id}/unbind`,旧后端不会通过改按钮文案或组合未确认操作假装完成。变更后以最新读取状态为准,未完成操作保留原身份供显式恢复;失败在当前窗口可见,结果未知不自动重放。服务端显示微信退出仍在处理中时,禁止新的连接、启停和绑定变更,保留刷新与原操作恢复;管理写入前再次读取当前状态。客户端已知目标智能体停用时明确显示不可用,并禁止恢复自动回复,仍可更换目标或安全暂停、解绑。旧请求无法安全恢复、账号关系不一致或目标不可用时提示先核对,保留原记录并避免盲目重试;账号关系或目标智能体问题可显式刷新状态后再次确认原操作,刷新本身不重放操作;“不再提醒”只移除本机提醒,不取消服务端仍在处理的操作。最新 Yuxi 的退出保护条件和持久恢复由服务端负责,发布需配套 Core/Adapter 与 Yuxi business=13、knowledge=2 数据库版本。渠道页不展示微信聊天或活动,既有历史、费用、内容隔离与应用 API 保持原边界。实际微信身份及重复扫码识别仍由云端负责,不从备注推测。 -- `Makelore Canvas|AI 绘画`:每个设计项目(Workspace)维护一份从创建起就存在的 Living Form。左侧项目栏沿用 Agents 的图标徽章与文字入口,分组标题为“我的项目”,桌面项目列表仅显示名称,隐藏名称下的设计版本与更新时间;新建、切换和项目操作菜单集中管理 Workspace。工作区统一项目标题、设计版本与制作状态,创作对话和作品区支持拖动分栏、键盘调宽及单栏展开;对话以 Markdown 呈现,底部输入器保留草稿、发送状态与 Enter / Shift+Enter 快捷键。右侧通过“当前作品 / 历史作品”切换,在大图或视频预览旁编辑唯一制作方案、确认费用和制作;历史作品可回到工作区查看,切换面板保留未保存的制作参数。紧凑窗口以“创作对话 / 作品与方案”切换面板,通过项目抽屉管理 Workspace。图片支持适应窗口、原始尺寸及 Esc 关闭,视频支持预览和保存到电脑。参考图从本地上传后以 `@图片N` 绑定,具体用法只写在创作提示词中。 +- `Makelore Canvas|AI 绘画`:每个设计项目(Workspace)维护一份从创建起就存在的 Living Form。左侧工作栏与 Code 共用间距、图标徽章、项目行、操作菜单和底部广场/账户样式,仅工作栏底色采用淡紫色(`#f5f2fa`);顶部返回箭头与 Code 对齐,分组标题为“我的项目”,桌面项目列表仅显示名称,隐藏名称下的设计版本与更新时间;新建、切换和项目操作菜单集中管理 Workspace。工作区统一项目标题、设计版本与制作状态,创作对话和作品区支持拖动分栏、键盘调宽及单栏展开;对话以 Markdown 呈现,底部输入器保留草稿、发送状态与 Enter / Shift+Enter 快捷键。右侧通过“当前作品 / 历史作品”切换,在大图或视频预览旁编辑唯一制作方案、确认费用和制作;历史作品可回到工作区查看,切换面板保留未保存的制作参数。紧凑窗口以“创作对话 / 作品与方案”切换面板,通过项目抽屉管理 Workspace。图片支持适应窗口、原始尺寸及 Esc 关闭,视频支持预览和保存到电脑。参考图从本地上传后以 `@图片N` 绑定,具体用法只写在创作提示词中。 - `Makelore Robot|AI 机器`:管理机器人智能体、设备激活绑定、智能体配置与设备分配;机器人工作台的智能体位于 Robot 全局侧栏,选中后在内容区先查看绑定设备、再查看基础设置,当前智能体通过 URL 参数保持可分享选择;绑定设备时默认先选择“引导配网”或“已有激活码”。在 Windows 与 macOS 的引导路径中,Makelore 可在弹窗内扫描并连接附近开放的 `Xiaozhi-*` 配网热点,失败时仍可通过系统 Wi-Fi 手动连接;后续继续复用机器人现有热点配网页面,不修改固件,也不由 Makelore 接收 Wi-Fi 密码。 应用启动默认进入 AI 模块入口选择页。入口页可在未登录状态浏览;未登录用户点击已开通模块时进入客户端原生登录页,可使用账号密码或手机号短信验证码登录。密码登录可选“记住密码”:正式安装包仅由 Electron Main 使用系统受保护凭据存储加密保存和回填账号密码,不写入 Renderer 持久状态,未打包开发版或系统安全存储不可用时禁用该选项。登录请求由 Renderer 经 Host API 交给 Electron Main,再由 Main 调用 Works Square;成功后回到入口选择页。已登录时,Electron Main 会从 Works Square `/api/auth/me` 读取当前账号,只向 Renderer 投影用户名、账号/租户/部门标识、权限名列表与四个模块布尔开关,不透传上游资料或凭据。工作区门禁同时要求有效 Token 和完整用户身份;旧状态缺失身份时会先尝试从 Main 恢复,仍无法确认则清除残留会话并返回登录页。被管理员关闭的模块会在入口页置灰且无法点击,直接访问其工作区路径也会返回入口页。旧服务端未返回策略或缺少单项字段时默认开放;这个客户端门禁不替代服务端 API 授权。 diff --git a/src/components/layout/ImageWorkspaceSidebar.tsx b/src/components/layout/ImageWorkspaceSidebar.tsx index 637cd34f..9a9c5a5a 100644 --- a/src/components/layout/ImageWorkspaceSidebar.tsx +++ b/src/components/layout/ImageWorkspaceSidebar.tsx @@ -21,7 +21,6 @@ import { } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { IMAGE_WORKSPACE_CREATE_PROJECT_EVENT } from '@/lib/image-workspace'; -import { cn } from '@/lib/utils'; import { useImageWorkspaceStore } from '@/stores/image-workspace'; import { useAuthStore } from '@/stores/auth'; import type { DesignWorkspaceSummary } from '../../../shared/image-workspace'; @@ -138,7 +137,7 @@ export function ImageWorkspaceSidebar({ sidebarCollapsed }: ImageWorkspaceSideba }; return ( -
+
{sidebarCollapsed ? ( -
+ -
- 我的项目 - {(status === 'error' || status === 'unavailable') && ( - - )} -
- -
- {status === 'loading' && ( -
- {[0, 1, 2].map((item) => ( -
- ))} -
- )} - - {(status === 'error' || status === 'unavailable') && ( -
- {workspaceError ?? 'AI 设计暂时不可用'} -
- )} - - {status === 'ready' && sortedProjects.length === 0 && ( - - )} - - {sortedProjects.map((project) => { - const active = project.workspaceId === activeWorkspaceId; - const pending = project.workspaceId === activeWorkspaceId && !workspace; - return ( -
+
+ 我的项目 + {(status === 'error' || status === 'unavailable') && ( + -
- - - - - - - openRenameDialog(project)} - > - - { - setDeleteTarget(project); - setDeleteConfirmation(''); - setDeleteError(null); - }} - > - - - - -
+ + + )} +
+ +
+ {status === 'loading' && ( +
+ {[0, 1, 2].map((item) => ( +
+ ))}
- ); - })} + )} + + {(status === 'error' || status === 'unavailable') && ( +
+ {workspaceError ?? 'AI 设计暂时不可用'} +
+ )} + + {status === 'ready' && sortedProjects.length === 0 && ( + + )} + + {sortedProjects.map((project) => { + const active = project.workspaceId === activeWorkspaceId; + const pending = project.workspaceId === activeWorkspaceId && !workspace; + return ( +
+ +
+ + + + + + + openRenameDialog(project)} + > + + { + setDeleteTarget(project); + setDeleteConfirmation(''); + setDeleteError(null); + }} + > + + + + +
+
+ ); + })} +
)} diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index fe0f6836..e05ff0f3 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -41,6 +41,7 @@ import { PointWallet } from '@/components/account/PointWallet'; import { AccountMenuTrigger } from '@/components/account/AccountMenuTrigger'; import { canLeaveCurrentPage, getSettingsReturnState } from '@/lib/settings-navigation'; import { cn } from '@/lib/utils'; +import { observeNativeFullscreen } from '@/lib/window-fullscreen'; import { useCurrentUserProfile } from '@/hooks/use-current-user-profile'; import type { SidebarPeekSource } from './sidebar-peek'; import { ModuleSwitcher } from './ModuleSwitcher'; @@ -86,6 +87,20 @@ type SidebarProps = { onSidebarPeekChange?: (open: boolean, source: SidebarPeekSource) => void; }; +function CanvasSidebarNavigation() { + const nativeTrafficLights = window.electron?.platform === 'darwin'; + const [nativeFullscreen, setNativeFullscreen] = useState(false); + useEffect(() => { + if (nativeTrafficLights) return observeNativeFullscreen(setNativeFullscreen); + }, [nativeTrafficLights]); + + return ( +
+ +
+ ); +} + export function Sidebar({ workspaceLayout = false, hideOnCompact = false, @@ -408,6 +423,7 @@ export function Sidebar({ id={workspaceLayout ? 'classroom-sidebar' : undefined} data-testid="sidebar" data-classroom={workspaceLayout} + data-workspace-sidebar={isPaintingModule ? 'canvas' : undefined} aria-hidden={sidebarCollapsed} onPointerEnter={() => onSidebarPeekChange?.(true, 'sidebar')} onPointerLeave={() => onSidebarPeekChange?.(false, 'sidebar')} @@ -432,8 +448,8 @@ export function Sidebar({ {activeModule !== 'cloud_agents' &&
@@ -441,7 +457,7 @@ export function Sidebar({ {workspaceLayout && !['darwin', 'win32'].includes(window.electron?.platform ?? '') &&
} - {!workspaceLayout && } + {isPaintingModule ? : !workspaceLayout && }
} @@ -874,7 +890,7 @@ export function Sidebar({ onOpenChange={setProfileDialogOpen} /> - {workspaceLayout && isProgrammingModule && ( + {((workspaceLayout && isProgrammingModule) || isPaintingModule) && (
diff --git a/src/pages/Chat/classroom-workspace.css b/src/pages/Chat/classroom-workspace.css index cc9f2b36..b03e6de0 100644 --- a/src/pages/Chat/classroom-workspace.css +++ b/src/pages/Chat/classroom-workspace.css @@ -1,36 +1,36 @@ -/* Shared Code navigation skin; chat content remains scoped to its workspace. */ -[data-testid="main-layout"][data-classroom="true"], [data-testid="sidebar"][data-classroom="true"], .classroom-workspace { +/* Shared Code/Canvas navigation skin; workspace content stays independently scoped. */ +[data-testid="main-layout"][data-classroom="true"], [data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]), .classroom-workspace { --background: 0 0% 100%; --foreground: 214 34% 23%; --muted-foreground: 214 16% 44%; --border: 213 31% 90%; } [data-testid="main-layout"][data-code-sidebar="true"] [data-testid="titlebar-sidebar-surface"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; } -[data-testid="sidebar"][data-classroom="true"] { background: #f3f7fb; border-color: #dde5ee; box-shadow: none; backdrop-filter: none; color: #26384d; } -[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-header { padding-top: 0; padding-bottom: 0; } -[data-testid="sidebar"][data-classroom="true"] [data-testid="classroom-sidebar-navigation"] { align-items: center; } -[data-testid="sidebar"][data-classroom="true"] .classroom-brand { height: 46px; gap: 10px; padding: 0 2px; } -[data-testid="sidebar"][data-classroom="true"] .classroom-community-footer { flex-shrink: 0; padding: 0 12px; } -[data-testid="sidebar"][data-classroom="true"] .classroom-community-footer + .classroom-account-footer { padding-top: 0; border-top: 0; } -[data-testid="sidebar"][data-classroom="true"] .classroom-community-footer .community-link[data-variant="sidebar"] { min-height: 48px; margin-top: 0; gap: 8px; padding: 8px; border: 0; border-radius: 8px; background: transparent; box-shadow: none; color: #26384d; font-size: 14px; font-weight: 600; line-height: 20px; white-space: normal; } -[data-testid="sidebar"][data-classroom="true"] .classroom-community-footer .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #eef3e7; } -[data-testid="sidebar"][data-classroom="true"] .community-link-icon { display: grid; place-items: center; flex-shrink: 0; width: 32px; height: 32px; border: 1px solid #dce5d0; border-radius: 12px; background: #f0f5e8; color: #526b46; } -[data-testid="sidebar"][data-classroom="true"] .community-link-icon svg { width: 20px; height: 20px; } -[data-testid="sidebar"][data-classroom="true"] .community-link-arrow { margin-left: auto; color: #64758a; } -[data-testid="sidebar"][data-classroom="true"] .classroom-brand:hover { background: #eaf1f8; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) { background: var(--workspace-sidebar-background, #f3f7fb); border-color: #dde5ee; box-shadow: none; backdrop-filter: none; color: #26384d; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-sidebar-header { padding-top: 0; padding-bottom: 0; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) [data-testid="classroom-sidebar-navigation"] { align-items: center; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-brand { height: 46px; gap: 10px; padding: 0 2px; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-community-footer { flex-shrink: 0; padding: 0 12px; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-community-footer + .classroom-account-footer { padding-top: 0; border-top: 0; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-community-footer .community-link[data-variant="sidebar"] { min-height: 48px; margin-top: 0; gap: 8px; padding: 8px; border: 0; border-radius: 8px; background: transparent; box-shadow: none; color: #26384d; font-size: 14px; font-weight: 600; line-height: 20px; white-space: normal; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-community-footer .community-link[data-variant="sidebar"]:hover:not(:disabled) { background: #eef3e7; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .community-link-icon { display: grid; place-items: center; flex-shrink: 0; width: 32px; height: 32px; border: 1px solid #dce5d0; border-radius: 12px; background: #f0f5e8; color: #526b46; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .community-link-icon svg { width: 20px; height: 20px; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .community-link-arrow { margin-left: auto; color: #64758a; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-brand:hover { background: #eaf1f8; } .classroom-brand-mark { display: grid; place-items: center; width: 40px; height: 42px; flex-shrink: 0; border: 1px solid #f1d1aa; border-radius: 12px; background: #fff0dd; box-shadow: 0 3px 0 #ecdbc5; } .classroom-brand-caption { display: block; margin-top: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 11px; color: #64758a; } -[data-testid="sidebar"][data-classroom="true"] .classroom-sidebar-content { padding: 0 12px 14px; } -[data-testid="sidebar"][data-classroom="true"] [data-testid="sidebar-create-project"] { min-height: 48px; gap: 8px; padding: 8px; border: 0; border-radius: 8px; background: transparent; color: #26384d; box-shadow: none; font-size: 14px; font-weight: 600; } -[data-testid="sidebar"][data-classroom="true"] [data-testid="sidebar-create-project"] svg { color: #c44b23; } -[data-testid="sidebar"][data-classroom="true"] [data-testid="sidebar-create-project"]:hover { background: #fff0e6; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-sidebar-content { padding: 0 12px 14px; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) [data-testid="sidebar-create-project"] { min-height: 48px; gap: 8px; padding: 8px; border: 0; border-radius: 8px; background: transparent; color: #26384d; box-shadow: none; font-size: 14px; font-weight: 600; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) [data-testid="sidebar-create-project"] svg { color: #c44b23; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) [data-testid="sidebar-create-project"]:hover { background: #fff0e6; } .classroom-create-project-icon { display: grid; place-items: center; flex-shrink: 0; width: 32px; height: 32px; border: 1px solid #f0d9c7; border-radius: 12px; background: #ffead9; } .classroom-create-project-icon svg { width: 20px; height: 20px; } -[data-testid="sidebar"][data-classroom="true"] .classroom-projects { margin-top: 8px; } -[data-testid="sidebar"][data-classroom="true"] .classroom-projects-heading { padding: 6px 3px; color: #64758a; } -[data-testid="sidebar"][data-classroom="true"] .classroom-projects-heading > svg { display: none; } -[data-testid="sidebar"][data-classroom="true"] .classroom-project-card { min-height: 48px; gap: 8px; padding: 8px 40px 8px 8px; border: 0; border-radius: 8px; box-shadow: none; } -[data-testid="sidebar"][data-classroom="true"] .classroom-project-card:hover { background: #eaf1f8; } -[data-testid="sidebar"][data-classroom="true"] .classroom-project-card[aria-current="page"] { background: hsl(var(--brand-soft)); box-shadow: none; } -[data-testid="sidebar"][data-classroom="true"] .classroom-project-card > span:last-child { font-size: 14px; font-weight: 600; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-projects { margin-top: 8px; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-projects-heading { padding: 6px 3px; color: #64758a; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-projects-heading > svg { display: none; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-project-card { min-height: 48px; gap: 8px; padding: 8px 40px 8px 8px; border: 0; border-radius: 8px; box-shadow: none; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-project-card:hover { background: #eaf1f8; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-project-card[aria-current="page"] { background: hsl(var(--brand-soft)); box-shadow: none; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-project-card > span:last-child { font-size: 14px; font-weight: 600; } .classroom-project-icon { display: grid; place-items: center; flex-shrink: 0; width: 32px; height: 32px; border: 1px solid #f0d9c7; border-radius: 12px; background: #ffead9; color: #a85026; } .classroom-project-icon svg { width: 20px; height: 20px; } .classroom-project-icon[data-tone="green"] { border-color: #cfe3c6; background: #e1f1d9; color: #507d39; } @@ -44,7 +44,10 @@ [data-testid="sidebar"][data-classroom="true"] .classroom-conversation-row[data-selected="true"] { background: #e0ecfa; border-color: #cedef0; color: #304f75; } [data-testid="sidebar"][data-classroom="true"] .classroom-conversation-row > button:first-child { min-height: 35px; gap: 7px; } [data-testid="sidebar"][data-classroom="true"] .classroom-conversation-row > button:first-child > svg { color: #7891af; } -[data-testid="sidebar"][data-classroom="true"] .classroom-account-footer { border-top: 1px solid #dde5ee; background: #edf3f9; } +[data-testid="sidebar"]:is([data-classroom="true"], [data-workspace-sidebar="canvas"]) .classroom-account-footer { border-top: 1px solid #dde5ee; background: var(--workspace-sidebar-background, #edf3f9); } +[data-testid="sidebar"][data-workspace-sidebar="canvas"] { --workspace-sidebar-background: #f5f2fa; } +[data-testid="sidebar"][data-workspace-sidebar="canvas"] .classroom-sidebar-header { padding: 0; } +[data-testid="sidebar"][data-workspace-sidebar="canvas"] .classroom-sidebar-content { display: flex; flex-direction: column; overflow: hidden; } .classroom-workspace [data-testid="coding-conversation-header"] { border-bottom-color: #e8edf3; background: #fff; color: #304968; } .classroom-tabs { display: flex; flex-shrink: 0; height: 46px; gap: 9px; padding: 6px 22px; border-bottom: 1px solid #e8edf3; } .classroom-workspace-toolbar { display: flex; flex-wrap: wrap; flex-shrink: 0; align-items: center; gap: 6px 12px; min-height: 46px; padding: 6px 22px; border-bottom: 1px solid #e8edf3; } diff --git a/tests/e2e/image-workspace-v2.spec.ts b/tests/e2e/image-workspace-v2.spec.ts index dbffecc4..64b5e2a3 100644 --- a/tests/e2e/image-workspace-v2.spec.ts +++ b/tests/e2e/image-workspace-v2.spec.ts @@ -473,6 +473,59 @@ test.describe('AI Design V2 workspace', () => { await expect(page.getByText('目标与用途')).toHaveCount(0); await expect(page.locator('[data-testid^="sidebar-image-conversation-"]')).toHaveCount(0); + // Canvas shares Code's navigation recipe, with a lavender surface and its own top return row. + const sidebar = page.getByTestId('sidebar'); + const createProject = sidebar.getByTestId('sidebar-create-project'); + const project = sidebar.getByTestId('sidebar-image-project-workspace-1'); + const projectsHeading = sidebar.locator('.classroom-projects-heading'); + const moduleReturn = sidebar.getByTestId('sidebar-module-switcher-trigger'); + const community = sidebar.getByRole('button', { name: '作品广场', exact: true }); + const account = sidebar.getByTestId('sidebar-member-menu-trigger'); + await expect(sidebar).toHaveAttribute('aria-hidden', 'false'); + await expect(sidebar).toHaveCSS('background-color', 'rgb(245, 242, 250)'); + await expect(createProject).toHaveText('新建项目'); + await expect(projectsHeading).toHaveText('我的项目'); + await expect(project).toHaveText('咖啡机新品主视觉'); + await expect(project).toHaveAttribute('aria-current', 'page'); + await expect(project.getByText('咖啡机新品主视觉', { exact: true })).toHaveCSS('font-size', '14px'); + await expect(project.getByText('咖啡机新品主视觉', { exact: true })).toHaveCSS('font-weight', '600'); + await expect(moduleReturn).toHaveText(''); + await expect(moduleReturn).toHaveAttribute('aria-label', '返回首页'); + await expect(moduleReturn).toHaveCSS('width', '32px'); + await expect(moduleReturn).toHaveCSS('height', '32px'); + for (const badge of [createProject.locator('.classroom-create-project-icon'), project.locator('.classroom-project-icon')]) { + await expect(badge).toHaveCSS('width', '32px'); + await expect(badge).toHaveCSS('height', '32px'); + await expect(badge.locator('svg')).toHaveCSS('width', '20px'); + await expect(badge.locator('svg')).toHaveCSS('height', '20px'); + } + const actions = sidebar.getByRole('button', { name: '项目操作:咖啡机新品主视觉', exact: true }); + await expect(actions).toHaveCSS('width', '32px'); + await expect(actions).toHaveCSS('height', '32px'); + const [sidebarBox, createBox, headingBox, projectBox, returnBox, communityBox, accountBox] = await Promise.all( + [sidebar, createProject, projectsHeading, project, moduleReturn, community, account].map((locator) => locator.boundingBox()), + ); + expect(sidebarBox!.width).toBe(256); + expect(createBox!.height).toBe(48); + expect(projectBox!.height).toBe(48); + expect(createBox!.y - sidebarBox!.y).toBe(40); + expect(headingBox!.y - sidebarBox!.y).toBe(96); + expect(projectBox!.y - sidebarBox!.y).toBe(136); + expect(returnBox!.y - sidebarBox!.y).toBe(4); + expect(communityBox!.x).toBe(createBox!.x); + expect(communityBox!.height).toBe(createBox!.height); + expect(communityBox!.y).toBeGreaterThan(projectBox!.y + projectBox!.height); + expect(accountBox!.y - communityBox!.y - communityBox!.height).toBeCloseTo(0, 0); + expect(sidebarBox!.y + sidebarBox!.height - accountBox!.y - accountBox!.height).toBeLessThanOrEqual(14); + const navigationStyles = await sidebar.evaluate((element) => { + const recipe = (selector: string) => { + const style = getComputedStyle(element.querySelector(selector)!); + return [style.borderRadius, style.borderWidth, style.boxShadow, style.padding, style.fontSize, style.fontWeight, style.lineHeight]; + }; + return { create: recipe('[data-testid="sidebar-create-project"]'), community: recipe('.community-link') }; + }); + expect(navigationStyles.community).toEqual(navigationStyles.create); + const splitBounds = (await split.boundingBox())!; const conversationBounds = (await conversationPane.boundingBox())!; const worksBounds = (await worksPane.boundingBox())!;