# Sidebar Section Layout 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 `position` and `maxHeight` props to `Sidebar.Section` so sections can stack at the top or bottom of the sidebar with optional scrollable content areas, and style all scrollbars to match the dark sidebar aesthetic. **Architecture:** Extend the existing flexbox column layout inside `SidebarRoot` by partitioning section children into top/bottom groups with a flex spacer between them. Each group wrapper scrolls independently when the viewport is short. `SidebarSection` gets a content wrapper div that accepts an inline `maxHeight` and scrolls its children. Custom thin scrollbar styles are applied via CSS. **Tech Stack:** React, CSS Modules, Vitest + React Testing Library --- ## File Structure | File | Action | Responsibility | |------|--------|----------------| | `src/design-system/layout/Sidebar/Sidebar.tsx` | Modify | Add `position` and `maxHeight` props to `SidebarSectionProps`, wrap children in `.sectionContent` div, partition children in `SidebarRoot` into top/bottom groups | | `src/design-system/layout/Sidebar/Sidebar.module.css` | Modify | Add `.sectionGroup`, `.sectionSpacer`, `.sectionContent` classes, custom scrollbar styles | | `src/design-system/layout/Sidebar/Sidebar.test.tsx` | Modify | Add tests for position partitioning, maxHeight content wrapper, spacer rendering, collapsed behavior | --- ### Task 1: Add CSS classes for section groups, spacer, content wrapper, and scrollbars **Files:** - Modify: `src/design-system/layout/Sidebar/Sidebar.module.css:392` (before the bottom links section) - [ ] **Step 1: Add `.sectionGroup`, `.sectionSpacer`, `.sectionContent`, and scrollbar styles** Insert the following block before the `/* ── Bottom links */` comment at line 392: ```css /* ── Section groups (top/bottom positioning) ───────────────────────────── */ .sectionGroup { flex: 0 1 auto; overflow-y: auto; min-height: 0; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.15) transparent; } .sectionGroup::-webkit-scrollbar { width: 4px; } .sectionGroup::-webkit-scrollbar-track { background: transparent; } .sectionGroup::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 2px; } .sectionGroup::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); } .sectionSpacer { flex: 1 0 0; } /* ── Section content (scrollable maxHeight) ────────────────────────────── */ .sectionContent { overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.15) transparent; } .sectionContent::-webkit-scrollbar { width: 4px; } .sectionContent::-webkit-scrollbar-track { background: transparent; } .sectionContent::-webkit-scrollbar-thumb { background: rgba(255, 255, 255, 0.15); border-radius: 2px; } .sectionContent::-webkit-scrollbar-thumb:hover { background: rgba(255, 255, 255, 0.3); } ``` - [ ] **Step 2: Commit** ```bash git add src/design-system/layout/Sidebar/Sidebar.module.css git commit -m "style: add section group, spacer, content, and scrollbar CSS classes" ``` --- ### Task 2: Add `maxHeight` prop and content wrapper to `SidebarSection` **Files:** - Modify: `src/design-system/layout/Sidebar/Sidebar.tsx:21-29` (SidebarSectionProps) - Modify: `src/design-system/layout/Sidebar/Sidebar.tsx:78-131` (SidebarSection component) - Test: `src/design-system/layout/Sidebar/Sidebar.test.tsx` - [ ] **Step 1: Write the failing tests** Add the following tests to the end of the `describe` block in `Sidebar.test.tsx`: ```tsx // 17. renders sectionContent wrapper with maxHeight when open it('renders section content wrapper with maxHeight style when open', () => { const { container } = render( ic} label="Apps" open onToggle={vi.fn()} maxHeight="200px" >
child
, ) const contentWrapper = container.querySelector('.sectionContent') expect(contentWrapper).toBeInTheDocument() expect(contentWrapper).toHaveStyle({ maxHeight: '200px' }) expect(screen.getByText('child')).toBeInTheDocument() }) // 18. renders sectionContent wrapper without maxHeight when not provided it('renders section content wrapper without inline maxHeight when maxHeight is not provided', () => { const { container } = render( ic} label="Apps" open onToggle={vi.fn()} >
child
, ) const contentWrapper = container.querySelector('.sectionContent') expect(contentWrapper).toBeInTheDocument() expect(contentWrapper).not.toHaveStyle({ maxHeight: '200px' }) expect(screen.getByText('child')).toBeInTheDocument() }) // 19. does not render sectionContent wrapper when section is closed it('does not render section content wrapper when section is closed', () => { const { container } = render( ic} label="Apps" open={false} onToggle={vi.fn()} maxHeight="200px" >
child
, ) const contentWrapper = container.querySelector('.sectionContent') expect(contentWrapper).not.toBeInTheDocument() }) ``` - [ ] **Step 2: Run tests to verify they fail** Run: `npx vitest run src/design-system/layout/Sidebar/Sidebar.test.tsx` Expected: FAIL — `maxHeight` prop is not recognized, no `.sectionContent` wrapper exists. - [ ] **Step 3: Add `position` and `maxHeight` to `SidebarSectionProps`** Update the interface at line 21: ```tsx interface SidebarSectionProps { icon: ReactNode label: string open: boolean onToggle: () => void active?: boolean children: ReactNode className?: string position?: 'top' | 'bottom' maxHeight?: string } ``` - [ ] **Step 4: Update `SidebarSection` to destructure new props and wrap children** Update the function signature at line 78 to destructure the new props (they are accepted but `position` is only used by `SidebarRoot`): ```tsx function SidebarSection({ icon, label, open, onToggle, active, children, className, position: _position, maxHeight, }: SidebarSectionProps) { ``` Then replace `{open && children}` (line 128) with: ```tsx {open && (
{children}
)} ``` - [ ] **Step 5: Run tests to verify they pass** Run: `npx vitest run src/design-system/layout/Sidebar/Sidebar.test.tsx` Expected: ALL PASS - [ ] **Step 6: Commit** ```bash git add src/design-system/layout/Sidebar/Sidebar.tsx src/design-system/layout/Sidebar/Sidebar.test.tsx git commit -m "feat: add maxHeight prop with sectionContent wrapper to SidebarSection" ``` --- ### Task 3: Partition children in `SidebarRoot` into top/bottom groups **Files:** - Modify: `src/design-system/layout/Sidebar/Sidebar.tsx:167-244` (SidebarRoot component) - Test: `src/design-system/layout/Sidebar/Sidebar.test.tsx` - [ ] **Step 1: Write the failing tests** Add the following tests to the end of the `describe` block in `Sidebar.test.tsx`: ```tsx // 20. renders top sections in sectionGroup wrapper it('renders sections in top group wrapper by default', () => { const { container } = render( ic} label="Apps" open onToggle={vi.fn()}>
apps content
, ) const topGroup = container.querySelector('.sectionGroup') expect(topGroup).toBeInTheDocument() expect(topGroup!.textContent).toContain('Apps') }) // 21. renders bottom sections in separate group with spacer it('renders bottom sections in a separate group with spacer between', () => { const { container } = render( ic} label="Apps" open onToggle={vi.fn()}>
apps content
ic} label="Routes" open onToggle={vi.fn()} position="bottom">
routes content
, ) const groups = container.querySelectorAll('.sectionGroup') expect(groups).toHaveLength(2) expect(groups[0].textContent).toContain('Apps') expect(groups[1].textContent).toContain('Routes') const spacer = container.querySelector('.sectionSpacer') expect(spacer).toBeInTheDocument() }) // 22. does not render spacer when no bottom sections it('does not render spacer when all sections are top', () => { const { container } = render( ic} label="Apps" open onToggle={vi.fn()}>
apps content
ic} label="Agents" open onToggle={vi.fn()}>
agents content
, ) const spacer = container.querySelector('.sectionSpacer') expect(spacer).not.toBeInTheDocument() }) // 23. collapsed sidebar renders bottom sections in bottom group it('renders bottom sections in bottom group when sidebar is collapsed', () => { const { container } = render( ic} label="Apps" open={false} onToggle={vi.fn()}>
apps
ic} label="Routes" open={false} onToggle={vi.fn()} position="bottom">
routes
, ) const groups = container.querySelectorAll('.sectionGroup') expect(groups).toHaveLength(2) // Bottom group should contain the Routes rail item const bottomGroup = groups[1] expect(bottomGroup.querySelector('[title="Routes"]')).toBeInTheDocument() }) ``` - [ ] **Step 2: Run tests to verify they fail** Run: `npx vitest run src/design-system/layout/Sidebar/Sidebar.test.tsx` Expected: FAIL — no `.sectionGroup` wrappers exist yet, sections render directly in `{rest}`. - [ ] **Step 3: Rewrite the child partitioning logic in `SidebarRoot`** Replace the entire IIFE inside the `