# Sidebar Redesign Implementation Plan > **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Replace the sidebar's pinned flows section with a daily stats bar + activity feed, reorganize nav into Resolve/Build/Insights groups, and split AI Assistant into FlowPilot + Flow Assist. **Architecture:** New lightweight backend endpoint (`/sessions/sidebar-stats`) returns daily stats and active/recent session data in a single call. Frontend restructures `Sidebar.tsx` to render stats bar, activity feed, and grouped nav. Pinned flows system removed from frontend only (backend tables/endpoints left for future cleanup). **Tech Stack:** Python FastAPI + SQLAlchemy async (backend), React + TypeScript + Zustand + Tailwind (frontend), Lucide React icons. **Spec:** `docs/superpowers/specs/2026-03-15-sidebar-redesign-design.md` --- ## File Map ### Backend (new) - `backend/app/schemas/sidebar.py` — Pydantic schemas for sidebar stats response - `backend/app/api/endpoints/sidebar.py` — `GET /sessions/sidebar-stats` endpoint ### Backend (modify) - `backend/app/api/router.py` — register new sidebar router ### Frontend (new) - `frontend/src/components/sidebar/SidebarStatsBar.tsx` — three-stat row component - `frontend/src/components/sidebar/SidebarActivityFeed.tsx` — activity feed (active + recents) - `frontend/src/components/sidebar/ActivityItem.tsx` — single activity row - `frontend/src/api/sidebar.ts` — API client for sidebar stats - `frontend/src/pages/FlowAssistPage.tsx` — standalone flow builder AI page ### Frontend (modify) - `frontend/src/components/layout/Sidebar.tsx` — major restructure - `frontend/src/router.tsx` — add `/flow-assist` route - `frontend/src/api/index.ts` — export new sidebar API - `frontend/src/pages/TreeLibraryPage.tsx` — remove pin integration - `frontend/src/components/library/TreeGridView.tsx` — remove pin button - `frontend/src/components/library/TreeListView.tsx` — remove pin button - `frontend/src/components/library/TreeTableView.tsx` — remove pin button ### Frontend (delete) - `frontend/src/components/sidebar/PinnedFlowsSection.tsx` - `frontend/src/store/pinnedFlowsStore.ts` - `frontend/src/api/pinnedFlows.ts` ### Tests (new) - `backend/tests/test_sidebar_stats.py` — integration tests for sidebar endpoint --- ## Chunk 1: Backend — Sidebar Stats Endpoint ### Task 1: Sidebar Stats Schema **Files:** - Create: `backend/app/schemas/sidebar.py` - [ ] **Step 1: Create the Pydantic schemas** ```python """Schemas for sidebar stats endpoint.""" from datetime import datetime from typing import Optional from uuid import UUID from pydantic import BaseModel class SidebarActiveSession(BaseModel): """An active or paused session for the activity feed.""" session_id: UUID tree_name: str tree_id: UUID tree_type: str started_at: datetime ticket_number: Optional[str] = None psa_ticket_id: Optional[str] = None class SidebarRecentSession(BaseModel): """A recently completed session for the activity feed.""" session_id: UUID tree_name: str tree_id: UUID tree_type: str completed_at: datetime class SidebarTreeCounts(BaseModel): """Tree counts for All Flows sub-items.""" total: int troubleshooting: int procedural: int maintenance: int class SidebarStatsResponse(BaseModel): """Response for GET /sessions/sidebar-stats.""" resolved_today: int active_count: int total_session_minutes_today: int tree_counts: SidebarTreeCounts active_sessions: list[SidebarActiveSession] recent_completions: list[SidebarRecentSession] ``` - [ ] **Step 2: Commit** ```bash git add backend/app/schemas/sidebar.py git commit -m "feat: add sidebar stats Pydantic schemas" ``` --- ### Task 2: Sidebar Stats Endpoint — Tests First **Files:** - Create: `backend/tests/test_sidebar_stats.py` - [ ] **Step 3: Write failing tests** ```python """Integration tests for sidebar stats endpoint.""" import pytest from httpx import AsyncClient class TestSidebarStats: """Tests for GET /sessions/sidebar-stats.""" @pytest.mark.asyncio async def test_sidebar_stats_no_sessions( self, client: AsyncClient, auth_headers: dict ): """Empty stats when user has no sessions.""" response = await client.get( "/api/v1/sessions/sidebar-stats?tz_offset=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert data["resolved_today"] == 0 assert data["active_count"] == 0 assert data["total_session_minutes_today"] == 0 assert data["active_sessions"] == [] assert data["recent_completions"] == [] @pytest.mark.asyncio async def test_sidebar_stats_with_active_session( self, client: AsyncClient, auth_headers: dict, test_tree: dict ): """Active session appears in activity feed.""" # Create a session (creates as active with started_at set) create_resp = await client.post( "/api/v1/sessions", json={"tree_id": test_tree["id"], "ticket_number": "TK-100"}, headers=auth_headers, ) assert create_resp.status_code == 201 response = await client.get( "/api/v1/sessions/sidebar-stats?tz_offset=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert data["active_count"] == 1 assert len(data["active_sessions"]) == 1 assert data["active_sessions"][0]["ticket_number"] == "TK-100" assert data["active_sessions"][0]["tree_id"] == test_tree["id"] @pytest.mark.asyncio async def test_sidebar_stats_resolved_today( self, client: AsyncClient, auth_headers: dict, test_tree: dict ): """Resolved session counts in resolved_today.""" # Create and complete a session create_resp = await client.post( "/api/v1/sessions", json={"tree_id": test_tree["id"]}, headers=auth_headers, ) session_id = create_resp.json()["id"] # Complete with resolved outcome await client.post( f"/api/v1/sessions/{session_id}/complete", json={"outcome": "resolved", "outcome_notes": "Fixed it"}, headers=auth_headers, ) response = await client.get( "/api/v1/sessions/sidebar-stats?tz_offset=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert data["resolved_today"] >= 1 assert data["active_count"] == 0 assert len(data["recent_completions"]) >= 1 @pytest.mark.asyncio async def test_sidebar_stats_max_active_sessions( self, client: AsyncClient, auth_headers: dict, test_tree: dict ): """Active sessions capped at 5.""" # Create 7 active sessions for i in range(7): await client.post( "/api/v1/sessions", json={"tree_id": test_tree["id"], "ticket_number": f"TK-{i}"}, headers=auth_headers, ) response = await client.get( "/api/v1/sessions/sidebar-stats?tz_offset=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert data["active_count"] == 7 # Total count is accurate assert len(data["active_sessions"]) == 5 # But list capped at 5 @pytest.mark.asyncio async def test_sidebar_stats_recent_completions_max_3( self, client: AsyncClient, auth_headers: dict, test_tree: dict ): """Recent completions capped at 3.""" # Create and complete 5 sessions for i in range(5): create_resp = await client.post( "/api/v1/sessions", json={"tree_id": test_tree["id"]}, headers=auth_headers, ) session_id = create_resp.json()["id"] await client.post( f"/api/v1/sessions/{session_id}/complete", json={"outcome": "resolved", "outcome_notes": "Done"}, headers=auth_headers, ) response = await client.get( "/api/v1/sessions/sidebar-stats?tz_offset=0", headers=auth_headers, ) assert response.status_code == 200 data = response.json() assert len(data["recent_completions"]) == 3 @pytest.mark.asyncio async def test_sidebar_stats_requires_auth(self, client: AsyncClient): """Endpoint requires authentication.""" response = await client.get("/api/v1/sessions/sidebar-stats?tz_offset=0") assert response.status_code == 401 @pytest.mark.asyncio async def test_sidebar_stats_requires_tz_offset( self, client: AsyncClient, auth_headers: dict ): """tz_offset query param is required.""" response = await client.get( "/api/v1/sessions/sidebar-stats", headers=auth_headers, ) assert response.status_code == 422 ``` - [ ] **Step 4: Run tests to verify they fail** ```bash cd backend && python -m pytest tests/test_sidebar_stats.py -v --override-ini="addopts=" ``` Expected: All tests FAIL (404 — endpoint doesn't exist yet). - [ ] **Step 5: Commit failing tests** ```bash git add backend/tests/test_sidebar_stats.py git commit -m "test: add failing tests for sidebar stats endpoint" ``` --- ### Task 3: Sidebar Stats Endpoint — Implementation **Files:** - Create: `backend/app/api/endpoints/sidebar.py` - Modify: `backend/app/api/router.py` - [ ] **Step 6: Implement the sidebar stats endpoint** Create `backend/app/api/endpoints/sidebar.py`: ```python """Sidebar stats and activity feed endpoint.""" from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Depends, Query from sqlalchemy import func, select, and_, case from sqlalchemy.ext.asyncio import AsyncSession from app.core.database import get_db from app.api.deps import get_current_active_user from app.models.session import Session from app.models.tree import Tree from app.models.user import User from app.schemas.sidebar import ( SidebarActiveSession, SidebarRecentSession, SidebarStatsResponse, SidebarTreeCounts, ) router = APIRouter(prefix="/sessions", tags=["sidebar"]) @router.get("/sidebar-stats", response_model=SidebarStatsResponse) async def get_sidebar_stats( tz_offset: int = Query(..., description="Client UTC offset in minutes (e.g. -300 for EST)"), current_user: User = Depends(get_current_active_user), db: AsyncSession = Depends(get_db), ) -> SidebarStatsResponse: """Get sidebar stats and activity feed for the current user. Returns daily stats (resolved count, active count, time in session) and lists of active + recently completed sessions. """ # Compute "today" start in user's timezone, then convert to UTC now_utc = datetime.now(timezone.utc) user_tz = timezone(timedelta(minutes=-tz_offset)) now_local = now_utc.astimezone(user_tz) today_start_local = now_local.replace(hour=0, minute=0, second=0, microsecond=0) today_start_utc = today_start_local.astimezone(timezone.utc) user_filter = Session.user_id == current_user.id # --- Resolved today --- resolved_result = await db.execute( select(func.count()).where( and_( user_filter, Session.completed_at >= today_start_utc, Session.outcome == "resolved", ) ) ) resolved_today = resolved_result.scalar() or 0 # --- Active count (all time, not just today) --- active_result = await db.execute( select(func.count()).where( and_( user_filter, Session.started_at.isnot(None), Session.completed_at.is_(None), ) ) ) active_count = active_result.scalar() or 0 # --- Total session minutes today --- # Sum of (completed_at or now) - started_at for sessions started today duration_expr = func.extract( "epoch", func.coalesce(Session.completed_at, now_utc) - Session.started_at, ) / 60.0 duration_result = await db.execute( select(func.coalesce(func.sum(duration_expr), 0)).where( and_( user_filter, Session.started_at.isnot(None), Session.started_at >= today_start_utc, ) ) ) total_minutes = int(duration_result.scalar() or 0) # --- Active sessions (max 5, most recent first) --- active_sessions_result = await db.execute( select( Session.id, Session.tree_id, Session.started_at, Session.ticket_number, Session.psa_ticket_id, Session.tree_snapshot["name"].as_string().label("tree_name"), Session.tree_snapshot["tree_type"].as_string().label("tree_type"), ) .where( and_( user_filter, Session.started_at.isnot(None), Session.completed_at.is_(None), ) ) .order_by(Session.started_at.desc()) .limit(5) ) active_sessions = [ SidebarActiveSession( session_id=row.id, tree_name=row.tree_name or "Unknown Flow", tree_id=row.tree_id, tree_type=row.tree_type or "troubleshooting", started_at=row.started_at, ticket_number=row.ticket_number, psa_ticket_id=row.psa_ticket_id, ) for row in active_sessions_result.all() ] # --- Recent completions (max 3, completed today, most recent first) --- recent_result = await db.execute( select( Session.id, Session.tree_id, Session.completed_at, Session.tree_snapshot["name"].as_string().label("tree_name"), Session.tree_snapshot["tree_type"].as_string().label("tree_type"), ) .where( and_( user_filter, Session.completed_at.isnot(None), Session.completed_at >= today_start_utc, ) ) .order_by(Session.completed_at.desc()) .limit(3) ) recent_completions = [ SidebarRecentSession( session_id=row.id, tree_name=row.tree_name or "Unknown Flow", tree_id=row.tree_id, tree_type=row.tree_type or "troubleshooting", completed_at=row.completed_at, ) for row in recent_result.all() ] # --- Tree counts (for All Flows sub-items) --- tree_counts_result = await db.execute( select( func.count().label("total"), func.count().filter(Tree.tree_type == "troubleshooting").label("troubleshooting"), func.count().filter(Tree.tree_type == "procedural").label("procedural"), func.count().filter(Tree.tree_type == "maintenance").label("maintenance"), ).where( and_( Tree.account_id == current_user.account_id, Tree.is_active.is_(True), Tree.deleted_at.is_(None), ) ) ) tc = tree_counts_result.one() return SidebarStatsResponse( resolved_today=resolved_today, active_count=active_count, total_session_minutes_today=total_minutes, tree_counts=SidebarTreeCounts( total=tc.total, troubleshooting=tc.troubleshooting, procedural=tc.procedural, maintenance=tc.maintenance, ), active_sessions=active_sessions, recent_completions=recent_completions, ) ``` - [ ] **Step 7: Register the router in `router.py`** In `backend/app/api/router.py`, add the import and registration. The sidebar router must be registered BEFORE the sessions router since both use `/sessions` prefix — FastAPI matches routes in registration order, and `/sessions/sidebar-stats` needs to match before `/sessions/{session_id}`. ```python from app.api.endpoints import sidebar # ... existing imports ... # Add BEFORE sessions router registration: api_router.include_router(sidebar.router) ``` - [ ] **Step 8: Run tests to verify they pass** ```bash cd backend && python -m pytest tests/test_sidebar_stats.py -v --override-ini="addopts=" ``` Expected: All 7 tests PASS. - [ ] **Step 9: Commit** ```bash git add backend/app/api/endpoints/sidebar.py backend/app/api/router.py backend/app/schemas/sidebar.py git commit -m "feat: add sidebar stats endpoint with daily stats and activity feed" ``` --- ## Chunk 2: Frontend — Sidebar API Client & Activity Components ### Task 4: Sidebar API Client **Files:** - Create: `frontend/src/api/sidebar.ts` - Modify: `frontend/src/api/index.ts` - [ ] **Step 10: Create the sidebar API client** ```typescript import { apiClient } from './client' export interface SidebarActiveSession { session_id: string tree_name: string tree_id: string tree_type: 'troubleshooting' | 'procedural' | 'maintenance' started_at: string ticket_number: string | null psa_ticket_id: string | null } export interface SidebarRecentSession { session_id: string tree_name: string tree_id: string tree_type: 'troubleshooting' | 'procedural' | 'maintenance' completed_at: string } export interface SidebarTreeCounts { total: number troubleshooting: number procedural: number maintenance: number } export interface SidebarStatsResponse { resolved_today: number active_count: number total_session_minutes_today: number tree_counts: SidebarTreeCounts active_sessions: SidebarActiveSession[] recent_completions: SidebarRecentSession[] } export const sidebarApi = { getStats: async (): Promise => { const tzOffset = new Date().getTimezoneOffset() const response = await apiClient.get( `/sessions/sidebar-stats?tz_offset=${tzOffset}` ) return response.data }, } ``` - [ ] **Step 11: Export from api/index.ts** Add to `frontend/src/api/index.ts`: ```typescript export { sidebarApi } from './sidebar' ``` - [ ] **Step 12: Commit** ```bash git add frontend/src/api/sidebar.ts frontend/src/api/index.ts git commit -m "feat: add sidebar stats API client" ``` --- ### Task 5: ActivityItem Component **Files:** - Create: `frontend/src/components/sidebar/ActivityItem.tsx` - [ ] **Step 13: Create the ActivityItem component** ```typescript import { useNavigate } from 'react-router-dom' import { getTreeNavigatePath } from '@/lib/routing' import { cn } from '@/lib/utils' interface ActivityItemProps { sessionId: string treeName: string treeId: string treeType: 'troubleshooting' | 'procedural' | 'maintenance' status: 'active' | 'paused' | 'recent' ticketNumber?: string | null timestamp?: string | null } function formatRelativeTime(dateString: string): string { const now = Date.now() const then = new Date(dateString).getTime() const diffMinutes = Math.floor((now - then) / 60000) if (diffMinutes < 1) return 'just now' if (diffMinutes < 60) return `${diffMinutes}m ago` const diffHours = Math.floor(diffMinutes / 60) if (diffHours < 24) return `${diffHours}h ago` return 'yesterday' } export function ActivityItem({ sessionId, treeName, treeId, treeType, status, ticketNumber, timestamp, }: ActivityItemProps) { const navigate = useNavigate() const handleClick = () => { navigate(getTreeNavigatePath(treeId, treeType), { state: { sessionId }, }) } const isRecent = status === 'recent' return ( ) } ``` - [ ] **Step 14: Commit** ```bash git add frontend/src/components/sidebar/ActivityItem.tsx git commit -m "feat: add ActivityItem component for sidebar activity feed" ``` --- ### Task 6: SidebarStatsBar Component **Files:** - Create: `frontend/src/components/sidebar/SidebarStatsBar.tsx` - [ ] **Step 15: Create the stats bar component** ```typescript interface SidebarStatsBarProps { resolved: number active: number sessionMinutes: number } function formatDuration(minutes: number): string { if (minutes < 60) return `${minutes}m` const h = Math.floor(minutes / 60) const m = minutes % 60 return m > 0 ? `${h}h ${m}m` : `${h}h` } export function SidebarStatsBar({ resolved, active, sessionMinutes }: SidebarStatsBarProps) { return (
{resolved}
Resolved
{active}
Active
{formatDuration(sessionMinutes)}
In Session
) } ``` - [ ] **Step 16: Commit** ```bash git add frontend/src/components/sidebar/SidebarStatsBar.tsx git commit -m "feat: add SidebarStatsBar component" ``` --- ### Task 7: SidebarActivityFeed Component **Files:** - Create: `frontend/src/components/sidebar/SidebarActivityFeed.tsx` - [ ] **Step 17: Create the activity feed component** ```typescript import { Clock } from 'lucide-react' import { useNavigate } from 'react-router-dom' import { ActivityItem } from './ActivityItem' import type { SidebarActiveSession, SidebarRecentSession } from '@/api/sidebar' interface SidebarActivityFeedProps { activeSessions: SidebarActiveSession[] recentCompletions: SidebarRecentSession[] totalActive: number } export function SidebarActivityFeed({ activeSessions, recentCompletions, totalActive, }: SidebarActivityFeedProps) { const navigate = useNavigate() const hasActivity = activeSessions.length > 0 || recentCompletions.length > 0 return (
{/* Header */}
Activity
{!hasActivity ? (

No activity today

) : (
{/* Active sessions */} {activeSessions.map((session) => ( ))} {/* Overflow link */} {totalActive > 5 && ( )} {/* Divider between active and recent */} {activeSessions.length > 0 && recentCompletions.length > 0 && (
)} {/* Recent completions */} {recentCompletions.map((session) => ( ))}
)}
) } ``` - [ ] **Step 18: Commit** ```bash git add frontend/src/components/sidebar/SidebarActivityFeed.tsx git commit -m "feat: add SidebarActivityFeed component" ``` --- ## Chunk 3: Frontend — Sidebar Restructure ### Task 8: Add Pulse Dot CSS Animation **Files:** - Modify: `frontend/src/index.css` - [ ] **Step 19: Add the pulse-dot keyframe animation** Add to `frontend/src/index.css` (in the global styles section, after existing keyframes): ```css @keyframes pulse-dot { 0%, 100% { box-shadow: 0 0 4px rgba(52,211,153,0.4); } 50% { box-shadow: 0 0 8px rgba(52,211,153,0.7); } } ``` - [ ] **Step 20: Commit** ```bash git add frontend/src/index.css git commit -m "feat: add pulse-dot animation for active session indicator" ``` --- ### Task 9: Restructure Sidebar.tsx **Files:** - Modify: `frontend/src/components/layout/Sidebar.tsx` This is the largest change. The sidebar is rewritten to use the new layout. - [ ] **Step 21: Rewrite Sidebar.tsx** Replace the entire file content. Key changes: 1. Remove `PinnedFlowsSection` import and `usePinnedFlowsStore` usage 2. Remove `sessionsApi` and `treesApi` data fetches (replaced by `sidebarApi.getStats`) 3. Add `SidebarStatsBar` and `SidebarActivityFeed` in expanded mode 4. Add `Brain` and `WandSparkles` icon imports 5. Reorganize nav items into Dashboard → Resolve → Build → Insights groups 6. Update collapsed view with all 13 items 7. Add `flowPilot` and `flowAssist` to `NAV_COLORS` The new `Sidebar.tsx` should: **Imports:** ```typescript import { useEffect, useState } from 'react' import { useLocation } from 'react-router-dom' import { LayoutGrid, Network, Wrench, Clock, FileOutput, BarChart3, Settings, PanelLeftClose, PanelLeftOpen, MessageSquareText, BookOpen, Lightbulb, Code2, Library, Brain, WandSparkles, } from 'lucide-react' import { cn } from '@/lib/utils' import { useUserPreferencesStore } from '@/store/userPreferencesStore' import { sidebarApi } from '@/api' import type { SidebarStatsResponse } from '@/api/sidebar' import { SidebarStatsBar } from '@/components/sidebar/SidebarStatsBar' import { SidebarActivityFeed } from '@/components/sidebar/SidebarActivityFeed' import { NavItem } from './NavItem' ``` **NAV_COLORS** — add two new entries, remove `ai`: ```typescript const NAV_COLORS = { dashboard: '#22d3ee', flows: '#a78bfa', editor: '#f59e0b', sessions: '#34d399', exports: '#60a5fa', flowPilot: '#e879f9', flowAssist:'#f472b6', stepLib: '#fb923c', scripts: '#2dd4bf', kb: '#fb7185', analytics: '#38bdf8', guides: '#a3e635', feedback: '#818cf8', } as const ``` **Data fetch** — replace the two separate fetches with one `sidebarApi.getStats()` call. Use `location.pathname` as a dependency so stats refresh on navigation (the sidebar persists across pages and does NOT re-mount): ```typescript const location = useLocation() const [stats, setStats] = useState(null) useEffect(() => { sidebarApi.getStats().then(setStats).catch(() => {}) }, [location.pathname]) ``` **Expanded layout** (inside the `else` branch): ```tsx {/* Stats Bar */} {/* Activity Feed */}
{/* Dashboard (standalone) */}
{/* Resolve group */}
Resolve
{/* Build group */}
Build
{/* Insights group */}
Insights
``` **Collapsed layout** — update to include all 13 items (add Brain/FlowPilot and WandSparkles/Flow Assist, remove BotMessageSquare/AI Assistant): ```tsx
``` - [ ] **Step 22: Verify the app builds** ```bash cd frontend && npm run build ``` Expected: Build succeeds. Fix any TypeScript or import errors. - [ ] **Step 23: Commit** ```bash git add frontend/src/components/layout/Sidebar.tsx git commit -m "feat: restructure sidebar with stats bar, activity feed, and grouped nav" ``` --- ### Task 10: Remove Pinned Flows Frontend Code **Files:** - Delete: `frontend/src/components/sidebar/PinnedFlowsSection.tsx` - Delete: `frontend/src/store/pinnedFlowsStore.ts` - Delete: `frontend/src/api/pinnedFlows.ts` - Modify: `frontend/src/pages/TreeLibraryPage.tsx` — remove pin integration - Modify: `frontend/src/components/library/TreeGridView.tsx` — remove pin button - Modify: `frontend/src/components/library/TreeListView.tsx` — remove pin button - [ ] **Step 24: Delete pinned flows files** ```bash rm frontend/src/components/sidebar/PinnedFlowsSection.tsx rm frontend/src/store/pinnedFlowsStore.ts rm frontend/src/api/pinnedFlows.ts ``` - [ ] **Step 25: Remove pin imports and props from TreeLibraryPage.tsx** Remove: - `usePinnedFlowsStore` import and all related hooks (`pinnedItems`, `isMutatingByTreeId`, `pinnedTreeIds`, `pinLoadingTreeIds`, `togglePin`, `loadPinned`) - The `useEffect` that calls `loadPinned()` - The `pinnedTreeIds`, `onTogglePin`, `pinLoadingTreeIds` props passed to `TreeGridView`, `TreeListView`, and `TreeTableView` - [ ] **Step 26: Remove pin button from TreeGridView.tsx** Remove: - `Star` icon import from lucide-react - `pinnedTreeIds`, `onTogglePin`, `pinLoadingTreeIds` from component props interface - The star/pin button JSX (lines ~67-86) - [ ] **Step 27: Remove pin button from TreeListView.tsx** Same pattern as TreeGridView — remove pin-related props and button. - [ ] **Step 27b: Remove pin button from TreeTableView.tsx** Same pattern as TreeGridView — remove pin-related props and button. - [ ] **Step 28: Remove pinnedFlows export from api/index.ts** Remove the `export { pinnedFlowsApi } from './pinnedFlows'` line (or similar). - [ ] **Step 29: Verify the app builds** ```bash cd frontend && npm run build ``` Expected: Build succeeds with no references to removed files. - [ ] **Step 30: Commit** ```bash git add frontend/src/components/sidebar/PinnedFlowsSection.tsx frontend/src/store/pinnedFlowsStore.ts frontend/src/api/pinnedFlows.ts frontend/src/pages/TreeLibraryPage.tsx frontend/src/components/library/TreeGridView.tsx frontend/src/components/library/TreeListView.tsx frontend/src/components/library/TreeTableView.tsx frontend/src/api/index.ts git commit -m "refactor: remove pinned flows frontend (PinnedFlowsSection, store, API, pin buttons)" ``` Note: The deleted files will show as "deleted" in `git add` — that's expected. Use `git add -u` as a fallback if the explicit paths don't stage the deletions correctly. --- ## Chunk 4: Frontend — Flow Assist Page & Route ### Task 11: FlowAssistPage **Files:** - Create: `frontend/src/pages/FlowAssistPage.tsx` - Modify: `frontend/src/router.tsx` - [ ] **Step 31: Create FlowAssistPage** This is a standalone page for the conversational flow builder. For now, create a placeholder page that will be expanded when the AI split is fully implemented. The page should use the same glass-card layout pattern as other pages. ```typescript import { WandSparkles } from 'lucide-react' export default function FlowAssistPage() { return (

Flow Assist

Build flows from natural language — describe what you need and Flow Assist will generate the decision tree or procedural steps.

Coming Soon

Flow Assist will be available here as a dedicated conversational flow builder. In the meantime, use the AI panel in the Flow Editor to generate flows.

) } ``` - [ ] **Step 32: Add the route in router.tsx** Add inside the protected route children (alongside other page routes like `/assistant`, `/scripts`, etc.): ```typescript { path: 'flow-assist', lazy: () => import('./pages/FlowAssistPage').then(m => ({ Component: m.default })), }, ``` If the router doesn't use lazy loading, use the direct import pattern matching the existing routes. - [ ] **Step 33: Verify the app builds** ```bash cd frontend && npm run build ``` - [ ] **Step 34: Commit** ```bash git add frontend/src/pages/FlowAssistPage.tsx frontend/src/router.tsx git commit -m "feat: add FlowAssistPage placeholder and /flow-assist route" ``` --- ## Chunk 5: Final Verification & Cleanup ### Task 12: Full Test Suite & Build Verification - [ ] **Step 35: Run backend tests** ```bash cd backend && python -m pytest tests/test_sidebar_stats.py -v --override-ini="addopts=" ``` Expected: All sidebar tests pass. - [ ] **Step 36: Run full backend test suite** ```bash cd backend && python -m pytest --override-ini="addopts=" -x -q ``` Expected: No regressions. If any pinned-flows tests exist and fail (because we didn't touch backend), that's expected — they should still pass since backend is unchanged. - [ ] **Step 37: Run frontend build** ```bash cd frontend && npm run build ``` Expected: Clean build, no errors. - [ ] **Step 38: Manual smoke test** Start the dev servers and verify: 1. Sidebar loads with stats bar showing 0/0/0m on fresh state 2. Activity feed shows "No activity today" when empty 3. Nav is grouped: Dashboard → Resolve (Sessions, All Flows, FlowPilot, Script Library) → Build (Flow Editor, Flow Assist, Step Library, KB Accelerator) → Insights (Exports, Analytics) 4. FlowPilot link goes to `/assistant` 5. Flow Assist link goes to `/flow-assist` (placeholder page) 6. Collapsed sidebar shows all 13 icon-only items 7. Pin buttons are gone from tree library grid/list views 8. No console errors - [ ] **Step 39: Final commit with any cleanup** If there are any remaining changes from smoke test fixes, stage the specific files and commit: ```bash git add git commit -m "chore: sidebar redesign cleanup and verification" ``` --- ## Summary | Chunk | Tasks | What it delivers | |-------|-------|-----------------| | 1 | Tasks 1-3 | Backend endpoint with tests — `GET /sessions/sidebar-stats` | | 2 | Tasks 4-7 | Frontend API client + ActivityItem, StatsBar, ActivityFeed components | | 3 | Tasks 8-10 | Sidebar restructure + pinned flows removal | | 4 | Task 11 | FlowAssistPage + `/flow-assist` route | | 5 | Task 12 | Full verification and cleanup |