Expand left sidebar on home module entry

This commit is contained in:
鲨鱼辣椒 committed 2026-10-14 11:14:43 +08:00
1 parent b7f50be87b
commit 1c39471156
4 files changed
+88 -2

No files matched your search

@@ -0,0 +1,41 @@
# Task: Expand module sidebars when entering from home
## Identity
- Task ID: 20261014-home-sidebar-default-31a87f9c
- Mode: Feature
- Branch: codex/20261014-home-sidebar-default-31a87f9c-home-sidebar-default
- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20261014-home-sidebar-default-31a87f9c
- Base commit: b7f50be87bac49bb321c28264279c7a9c379aee4
- Owner: codex
- Status: In Progress
## Scope
- Expand the shared left sidebar before an authorized homepage module-card navigation for Code, Agents, and Canvas. Cover prior-collapse/re-entry and preserve manual toggling.
## Intent And Constraints
- Scope is homepage entry behavior only; retain module access/auth guards, Agent view=mine entry, manual collapse, and accepted gallery placement. No new API or content values. Preserve current combined trial Main and inherited feature bytes during Renderer update.
## Outcome
- Implemented guarded homepage-entry expansion through the existing shared sidebar setter; already-expanded entries avoid redundant preference writes. Agent entry remains `?view=mine`; auth/module policy guards remain before changes. Manual toggling and Canvas desktop/compact rules are unchanged.
- README current behavior updated. Source review found no actionable issue.
- Combined-trial Renderer update and focused Electron verification pending.
## Verification
- Planning gate passed after official task identity/status and all 63 peer records (189 sections) were checked read-only; no blocking semantic conflict.
- Typecheck and targeted ESLint passed.
- 37 focused tests passed across module-navigation, cloud-agent-entry-navigation, main-layout-sidebar-peek, and image-workspace-sidebar.
- Default full `build:vite` passed with Renderer-only mode unset; existing Browserslist/chunk warnings only.
- Focused Electron and actual trial checks pending.
## Follow-ups
- None recorded.
## Promotion Candidates
- None recorded.
+1 -1
View File
@@ -8,7 +8,7 @@
Code 会话保存的模型被下架后,会提示重新选择可用模型;用户可在原会话中切换,恢复时使用新选择并保留会话与历史消息。切换不会自动重发消息或自动选择替代模型。 Code 会话保存的模型被下架后,会提示重新选择可用模型;用户可在原会话中切换,恢复时使用新选择并保留会话与历史消息。切换不会自动重发消息或自动选择替代模型。
Makelore 是一个面向软件、视觉创作、智能机器人与个人云智能体的 AI 桌面工作台。当前版本为 `2.0.0`,首页展示 Agents、Code、Canvas 三个模块入口,Robot 入口隐藏;云智能体需配套配置 WS/Yuxi 服务。桌面采用左侧问候与标题、右侧三个横向插画卡片竖排的布局,Code 入口中心与“今天,想创作什么?”标题中心对齐,三个入口等高、间距均匀;标题组在桌面向右偏移约 1.5cm,小窗口将标题放在卡片上方;Agents 标语为“打造你想象中的AI助手”。各页左下角账户按钮统一沿用 Code 的白色圆角卡片、头像、字号和右箭头,展开时跟随 Code 侧栏宽度偏好,首页和设置页与 Code 保持相同的左侧与底部边距;展开菜单保持原尺寸,可查看本人词元点数与预占、打开词元点数与充值、个人资料、设置和退出登录;沿用侧栏的账号、钱包和资料规则。设置使用独立页面,不显示编程项目栏、项目标题或工作栏收起按钮;“返回”回到打开设置前的页面(保留查询参数),直接打开设置时回模块首页,未保存设置先保存或还原。工作区左上角入口点击后返回模块入口页: Makelore 是一个面向软件、视觉创作、智能机器人与个人云智能体的 AI 桌面工作台。当前版本为 `2.0.0`,首页展示 Agents、Code、Canvas 三个模块入口,Robot 入口隐藏;云智能体需配套配置 WS/Yuxi 服务。桌面采用左侧问候与标题、右侧三个横向插画卡片竖排的布局,Code 入口中心与“今天,想创作什么?”标题中心对齐,三个入口等高、间距均匀;标题组在桌面向右偏移约 1.5cm,小窗口将标题放在卡片上方;Agents 标语为“打造你想象中的AI助手”。各页左下角账户按钮统一沿用 Code 的白色圆角卡片、头像、字号和右箭头,展开时跟随 Code 侧栏宽度偏好,首页和设置页与 Code 保持相同的左侧与底部边距;展开菜单保持原尺寸,可查看本人词元点数与预占、打开词元点数与充值、个人资料、设置和退出登录;沿用侧栏的账号、钱包和资料规则。设置使用独立页面,不显示编程项目栏、项目标题或工作栏收起按钮;“返回”回到打开设置前的页面(保留查询参数),直接打开设置时回模块首页,未保存设置先保存或还原。每次从首页进入 Code、Agents 或 Canvas 时,默认展开左侧工作栏;进入后沿用各模块现有的手动收起与紧凑布局规则。工作区左上角入口点击后返回模块入口页:
- `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。 - `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。
会话工作台采用浅蓝项目栏,侧栏入口与项目列表采用 Agent 式的彩色图标徽章+文字,选中项目保留浅色反馈;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;欢迎区的麦洛标识、星装饰和「作品广场」组成同一个可鼠标或键盘操作的广场入口,保留原有主标题与说明,输入框上方不再显示两句广场邀请。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏的「作品广场」固定在底部、紧邻账户按钮上方,保持与上方入口相同的横向对齐;只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。 会话工作台采用浅蓝项目栏,侧栏入口与项目列表采用 Agent 式的彩色图标徽章+文字,选中项目保留浅色反馈;中间对话/作品使用圆角标签,作品预览的后退、前进、刷新与网址输入统一放在标签右侧;只在作品页显示,窄窗口自动换行。右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;欢迎区的麦洛标识、星装饰和「作品广场」组成同一个可鼠标或键盘操作的广场入口,保留原有主标题与说明,输入框上方不再显示两句广场邀请。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏的「作品广场」固定在底部、紧邻账户按钮上方,保持与上方入口相同的横向对齐;只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。
+3
View File
@@ -12,6 +12,7 @@ import { aiModules, isAiModuleAllowed, type AiModuleId } from '@/lib/ai-modules'
import { getAuthUserDisplayName } from '@/lib/auth-user-display'; import { getAuthUserDisplayName } from '@/lib/auth-user-display';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { useAuthStore } from '@/stores/auth'; import { useAuthStore } from '@/stores/auth';
import { useSettingsStore } from '@/stores/settings';
import { getUserProfileDisplayName } from '@/stores/user-profile'; import { getUserProfileDisplayName } from '@/stores/user-profile';
const moduleSelectionContent: Record<AiModuleId, { label: string; image: string; imageAlt: string }> = { const moduleSelectionContent: Record<AiModuleId, { label: string; image: string; imageAlt: string }> = {
@@ -23,6 +24,7 @@ const moduleSelectionContent: Record<AiModuleId, { label: string; image: string;
export function ModuleSelection({ authRequired = true }: { authRequired?: boolean }) { export function ModuleSelection({ authRequired = true }: { authRequired?: boolean }) {
const navigate = useNavigate(); const navigate = useNavigate();
const setSidebarCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
const authUser = useAuthStore((state) => state.user); const authUser = useAuthStore((state) => state.user);
const moduleAccess = useAuthStore((state) => state.moduleAccess); const moduleAccess = useAuthStore((state) => state.moduleAccess);
const authenticated = useAuthStore((state) => state.isAuthenticated()); const authenticated = useAuthStore((state) => state.isAuthenticated());
@@ -46,6 +48,7 @@ export function ModuleSelection({ authRequired = true }: { authRequired?: boolea
navigate('/login'); navigate('/login');
return; return;
} }
if (useSettingsStore.getState().sidebarCollapsed) setSidebarCollapsed(false);
navigate(route === '/cloud-agents' ? `${route}?view=mine` : route); navigate(route === '/cloud-agents' ? `${route}?view=mine` : route);
}; };
+43 -1
View File
@@ -1,11 +1,12 @@
import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it } from 'vitest';
import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom'; import { MemoryRouter, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import { ModuleSwitcher } from '@/components/layout/ModuleSwitcher'; import { ModuleSwitcher } from '@/components/layout/ModuleSwitcher';
import { ModuleSelection } from '@/pages/ModuleSelection'; import { ModuleSelection } from '@/pages/ModuleSelection';
import { useAuthStore } from '@/stores/auth'; import { useAuthStore } from '@/stores/auth';
import { codingWorkspaceStore } from '@/stores/coding-workspace'; import { codingWorkspaceStore } from '@/stores/coding-workspace';
import { useUserProfileStore } from '@/stores/user-profile'; import { useUserProfileStore } from '@/stores/user-profile';
import { useSettingsStore } from '@/stores/settings';
const { hostApiFetchMock } = vi.hoisted(() => ({ const { hostApiFetchMock } = vi.hoisted(() => ({
hostApiFetchMock: vi.fn(), hostApiFetchMock: vi.fn(),
@@ -20,9 +21,22 @@ function LocationProbe() {
return <output data-testid="location-path">{location.pathname}</output>; return <output data-testid="location-path">{location.pathname}</output>;
} }
function SidebarEntryProbe() {
const collapsed = useSettingsStore((state) => state.sidebarCollapsed);
const setCollapsed = useSettingsStore((state) => state.setSidebarCollapsed);
const navigate = useNavigate();
return <>
<LocationProbe />
<output data-testid="sidebar-state">{collapsed ? 'collapsed' : 'expanded'}</output>
<button onClick={() => setCollapsed(!collapsed)}>Toggle sidebar</button>
<button onClick={() => navigate('/module-select')}>Home</button>
</>;
}
describe('AI module navigation', () => { describe('AI module navigation', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
useSettingsStore.setState({ sidebarCollapsed: true });
useAuthStore.setState({ useAuthStore.setState({
initialized: true, initialized: true,
loading: false, loading: false,
@@ -52,6 +66,32 @@ describe('AI module navigation', () => {
}); });
}); });
it.each([
['programming', '/chat'],
['cloud_agents', '/cloud-agents'],
['painting', '/image-canvas'],
])('expands %s on every home entry while allowing manual collapse', async (moduleId, route) => {
render(
<MemoryRouter initialEntries={['/module-select']}>
<Routes>
<Route path="/module-select" element={<ModuleSelection />} />
<Route path="*" element={<SidebarEntryProbe />} />
</Routes>
</MemoryRouter>,
);
fireEvent.click(screen.getByTestId(`ai-module-option-${moduleId}`));
expect(await screen.findByTestId('location-path')).toHaveTextContent(route);
expect(screen.getByTestId('sidebar-state')).toHaveTextContent('expanded');
fireEvent.click(screen.getByRole('button', { name: 'Toggle sidebar' }));
expect(screen.getByTestId('sidebar-state')).toHaveTextContent('collapsed');
fireEvent.click(screen.getByRole('button', { name: 'Home', exact: true }));
expect(useSettingsStore.getState().sidebarCollapsed).toBe(true);
fireEvent.click(screen.getByTestId(`ai-module-option-${moduleId}`));
expect(await screen.findByTestId('sidebar-state')).toHaveTextContent('expanded');
});
it('shows Agents, Code and Canvas on the home page and routes Canvas from the chooser', async () => { it('shows Agents, Code and Canvas on the home page and routes Canvas from the chooser', async () => {
render( render(
<MemoryRouter initialEntries={['/module-select']}> <MemoryRouter initialEntries={['/module-select']}>
@@ -144,6 +184,7 @@ describe('AI module navigation', () => {
fireEvent.click(canvasOption); fireEvent.click(canvasOption);
expect(screen.getByTestId('ai-module-selection-page')).toBeInTheDocument(); expect(screen.getByTestId('ai-module-selection-page')).toBeInTheDocument();
expect(screen.queryByTestId('location-path')).not.toBeInTheDocument(); expect(screen.queryByTestId('location-path')).not.toBeInTheDocument();
expect(useSettingsStore.getState().sidebarCollapsed).toBe(true);
}); });
it('routes AI programming from the chooser to the project conversation entry', async () => { it('routes AI programming from the chooser to the project conversation entry', async () => {
@@ -184,6 +225,7 @@ describe('AI module navigation', () => {
fireEvent.click(screen.getByTestId('ai-module-option-programming')); fireEvent.click(screen.getByTestId('ai-module-option-programming'));
expect(await screen.findByTestId('location-path')).toHaveTextContent('/login'); expect(await screen.findByTestId('location-path')).toHaveTextContent('/login');
expect(useSettingsStore.getState().sidebarCollapsed).toBe(true);
}); });
it('returns to the module chooser from the Sidebar entry', async () => { it('returns to the module chooser from the Sidebar entry', async () => {