Expand left sidebar on home module entry
This commit is contained in:
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.
|
||||||
@@ -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 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;欢迎区的麦洛标识、星装饰和「作品广场」组成同一个可鼠标或键盘操作的广场入口,保留原有主标题与说明,输入框上方不再显示两句广场邀请。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏的「作品广场」固定在底部、紧邻账户按钮上方,保持与上方入口相同的横向对齐;只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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 () => {
|
||||||
|
|||||||
Reference in new issue
Block a user