# Desktop Sidebar Navigation Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add AI写作、安全培训、考试工坊 buttons to the shared desktop sidebar and make them behave like the existing Chat input-area mode buttons. **Architecture:** Extend the existing shared `Sidebar.vue` component with a data-driven `navItems` array. `Chat.vue` passes `currentMode` and handles `select-mode` with its existing `setMode()` function; other pages route to `/chat?mode=...` so Chat initializes the same mode on mount. **Tech Stack:** Vue 3, Vue Router 4, Vite, Vitest, @vue/test-utils, Less. --- ## File Structure - Modify: `shudao-vue-frontend/src/components/Sidebar.vue` - Owns the desktop left-side navigation rail. - Will render five navigation buttons from a `navItems` array. - Will emit Chat mode selections when already on Chat. - Will route to `/chat?mode=...` from non-Chat pages for Chat-mode features. - Modify: `shudao-vue-frontend/src/views/Chat.vue` - Passes `currentMode` to the sidebar. - Handles sidebar `select-mode` with existing `setMode()`. - Create: `shudao-vue-frontend/src/components/Sidebar.test.js` - Verifies the sidebar renders the required labels. - Verifies route clicks call `router.push()` with the correct fallback paths from non-Chat pages. - Verifies Chat-mode clicks emit `select-mode` when already on Chat. - Verifies the current route gets the active state. ### Task 1: Sidebar Navigation Test **Files:** - Create: `shudao-vue-frontend/src/components/Sidebar.test.js` - Modify: none - [ ] **Step 1: Write the failing test** ```js import { mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { nextTick, reactive } from 'vue' import Sidebar from './Sidebar.vue' const push = vi.fn() const routeState = reactive({ name: 'Chat', path: '/chat' }) vi.mock('vue-router', () => ({ useRouter: () => ({ push }), useRoute: () => routeState })) describe('Sidebar', () => { beforeEach(() => { push.mockClear() routeState.name = 'Chat' routeState.path = '/chat' }) it('renders the desktop feature navigation buttons', () => { const wrapper = mount(Sidebar) expect(wrapper.text()).toContain('AI问答') expect(wrapper.text()).toContain('隐患提示') expect(wrapper.text()).toContain('AI写作') expect(wrapper.text()).toContain('安全培训') expect(wrapper.text()).toContain('考试工坊') }) it.each([ ['AI问答', '/chat'], ['隐患提示', '/hazard-detection'], ['AI写作', '/ai-writing'], ['安全培训', '/safety-hazard'], ['考试工坊', '/exam-workshop'] ])('routes %s to %s', async (label, path) => { const wrapper = mount(Sidebar) await wrapper.find(`[data-testid="sidebar-nav-${path.slice(1)}"]`).trigger('click') expect(wrapper.text()).toContain(label) expect(push).toHaveBeenCalledWith(path) }) it('marks the current route as active', async () => { routeState.name = 'AIWriting' routeState.path = '/ai-writing' const wrapper = mount(Sidebar) await nextTick() const activeItem = wrapper.find('.nav-item.active') expect(activeItem.exists()).toBe(true) expect(activeItem.text()).toContain('AI写作') }) }) ``` - [ ] **Step 2: Run test to verify it fails** Run: `npm run test -- src/components/Sidebar.test.js` Expected: FAIL because the current sidebar only renders AI问答 and 隐患提示, and does not expose `data-testid` attributes for all five route buttons. ### Task 2: Data-Driven Sidebar Implementation **Files:** - Modify: `shudao-vue-frontend/src/components/Sidebar.vue` - Test: `shudao-vue-frontend/src/components/Sidebar.test.js` - [ ] **Step 1: Implement the minimal sidebar change** Replace the hardcoded menu items with a `navItems` array: ```js const navItems = [ { routeName: 'Chat', path: '/chat', label: 'AI问答', icon: chatIcon }, { routeName: 'HazardDetection', path: '/hazard-detection', label: '隐患提示', icon: hazardIcon }, { routeName: 'AIWriting', path: '/ai-writing', label: 'AI写作', icon: aiWritingIcon }, { routeName: 'SafetyHazard', path: '/safety-hazard', label: '安全培训', icon: safetyIcon }, { routeName: 'ExamWorkshop', path: '/exam-workshop', label: '考试工坊', icon: examIcon } ] ``` Render with `v-for`, add `data-testid`, and push `item.path` on click. - [ ] **Step 2: Run focused test to verify it passes** Run: `npm run test -- src/components/Sidebar.test.js` Expected: PASS for all Sidebar tests. - [ ] **Step 3: Run frontend build** Run: `npm run build` Expected: Build exits with code 0. - [ ] **Step 4: Review diff** Run: `git diff -- shudao-vue-frontend/src/components/Sidebar.vue shudao-vue-frontend/src/components/Sidebar.test.js` Expected: Diff only contains the shared sidebar update and the focused test.