# 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<SidebarStatsResponse> => {
    const tzOffset = new Date().getTimezoneOffset()
    const response = await apiClient.get<SidebarStatsResponse>(
      `/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 (
    <button
      onClick={handleClick}
      className={cn(
        'flex w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left transition-colors',
        'hover:bg-[rgba(255,255,255,0.03)]',
        isRecent ? 'text-[#6b7280] text-[0.72rem]' : 'text-[#e2e8f0] text-[0.8rem]'
      )}
      title={`${treeName}${ticketNumber ? ` (${ticketNumber})` : ''} — click to resume`}
      aria-label={
        status === 'active'
          ? `Active session: ${treeName}`
          : status === 'paused'
            ? `Paused session: ${treeName}`
            : `Recent session: ${treeName}`
      }
    >
      {/* Status dot */}
      {status === 'active' && (
        <span
          className="h-[7px] w-[7px] shrink-0 rounded-full"
          style={{
            background: '#34d399',
            boxShadow: '0 0 6px rgba(52,211,153,0.5)',
            animation: 'pulse-dot 2s ease-in-out infinite',
          }}
          aria-label="Active session"
        />
      )}
      {status === 'paused' && (
        <span
          className="h-[7px] w-[7px] shrink-0 rounded-full"
          style={{
            background: '#f59e0b',
            boxShadow: '0 0 4px rgba(245,158,11,0.3)',
          }}
          aria-label="Paused session"
        />
      )}
      {status === 'recent' && (
        <span className="h-1 w-1 shrink-0 rounded-full bg-[#3d4350]" />
      )}

      {/* Flow name */}
      <span className="flex-1 truncate">{treeName}</span>

      {/* Ticket number or timestamp */}
      {ticketNumber && !isRecent && (
        <span className="shrink-0 font-label text-[0.5625rem] text-[#60a5fa]">
          {ticketNumber}
        </span>
      )}
      {isRecent && timestamp && (
        <span className="shrink-0 font-label text-[0.5625rem] text-[#5a6170]">
          {formatRelativeTime(timestamp)}
        </span>
      )}
    </button>
  )
}
```

- [ ] **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 (
    <div
      className="flex gap-0.5 px-3 pt-2 pb-1"
      role="group"
      aria-label="Today's stats"
    >
      <div className="flex-1 rounded-md bg-[rgba(255,255,255,0.02)] px-1 py-1.5 text-center">
        <div
          className="font-label text-sm font-semibold leading-none"
          style={{ color: '#34d399' }}
          aria-label={`${resolved} resolved today`}
        >
          {resolved}
        </div>
        <div className="mt-1 font-label text-[7px] uppercase tracking-[0.1em] text-[#3d4350]">
          Resolved
        </div>
      </div>
      <div className="flex-1 rounded-md bg-[rgba(255,255,255,0.02)] px-1 py-1.5 text-center">
        <div
          className="font-label text-sm font-semibold leading-none"
          style={{ color: '#22d3ee' }}
          aria-label={`${active} active sessions`}
        >
          {active}
        </div>
        <div className="mt-1 font-label text-[7px] uppercase tracking-[0.1em] text-[#3d4350]">
          Active
        </div>
      </div>
      <div className="flex-1 rounded-md bg-[rgba(255,255,255,0.02)] px-1 py-1.5 text-center">
        <div
          className="font-label text-sm font-semibold leading-none text-muted-foreground"
          aria-label={`${formatDuration(sessionMinutes)} in session today`}
        >
          {formatDuration(sessionMinutes)}
        </div>
        <div className="mt-1 font-label text-[7px] uppercase tracking-[0.1em] text-[#3d4350]">
          In Session
        </div>
      </div>
    </div>
  )
}
```

- [ ] **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 (
    <div className="px-3 pb-1">
      {/* Header */}
      <div className="flex items-center gap-1.5 px-2.5 py-1 mb-0.5">
        <Clock size={10} style={{ color: '#34d399' }} />
        <span className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170]">
          Activity
        </span>
      </div>

      {!hasActivity ? (
        <p className="px-2.5 py-2 text-xs text-muted-foreground">
          No activity today
        </p>
      ) : (
        <div className="space-y-0.5">
          {/* Active sessions */}
          {activeSessions.map((session) => (
            <ActivityItem
              key={session.session_id}
              sessionId={session.session_id}
              treeName={session.tree_name}
              treeId={session.tree_id}
              treeType={session.tree_type}
              status="active"
              ticketNumber={session.ticket_number || session.psa_ticket_id}
            />
          ))}

          {/* Overflow link */}
          {totalActive > 5 && (
            <button
              onClick={() => navigate('/sessions')}
              className="w-full px-2.5 py-1 text-left text-[0.6875rem] text-muted-foreground hover:text-foreground transition-colors"
            >
              View all in Sessions →
            </button>
          )}

          {/* Divider between active and recent */}
          {activeSessions.length > 0 && recentCompletions.length > 0 && (
            <div className="mx-2.5 my-1" style={{ height: '1px', background: 'rgba(255,255,255,0.03)' }} />
          )}

          {/* Recent completions */}
          {recentCompletions.map((session) => (
            <ActivityItem
              key={session.session_id}
              sessionId={session.session_id}
              treeName={session.tree_name}
              treeId={session.tree_id}
              treeType={session.tree_type}
              status="recent"
              timestamp={session.completed_at}
            />
          ))}
        </div>
      )}
    </div>
  )
}
```

- [ ] **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<SidebarStatsResponse | null>(null)

useEffect(() => {
  sidebarApi.getStats().then(setStats).catch(() => {})
}, [location.pathname])
```

**Expanded layout** (inside the `else` branch):
```tsx
{/* Stats Bar */}
<SidebarStatsBar
  resolved={stats?.resolved_today ?? 0}
  active={stats?.active_count ?? 0}
  sessionMinutes={stats?.total_session_minutes_today ?? 0}
/>

{/* Activity Feed */}
<SidebarActivityFeed
  activeSessions={stats?.active_sessions ?? []}
  recentCompletions={stats?.recent_completions ?? []}
  totalActive={stats?.active_count ?? 0}
/>

<div style={{ borderBottom: '1px solid var(--glass-border)' }} />

{/* Dashboard (standalone) */}
<div className="px-3 py-2 space-y-0.5">
  <NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} />

  {/* Resolve group */}
  <div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
    Resolve
  </div>
  <NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} />
  <NavItem
    href="/trees"
    icon={Network}
    label="All Flows"
    badge={stats?.tree_counts.total || undefined}
    iconColor={NAV_COLORS.flows}
    matchPaths={['/trees', '/flows']}
    children={[
      { href: '/trees?type=troubleshooting', label: 'Troubleshooting', count: stats?.tree_counts.troubleshooting || undefined },
      { href: '/trees?type=procedural', label: 'Projects', count: stats?.tree_counts.procedural || undefined },
      { href: '/trees?type=maintenance', label: 'Maintenance', count: stats?.tree_counts.maintenance || undefined },
    ]}
  />
  <NavItem href="/assistant" icon={Brain} label="FlowPilot" iconColor={NAV_COLORS.flowPilot} />
  <NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} />

  {/* Build group */}
  <div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
    Build
  </div>
  <NavItem href="/my-trees" icon={Wrench} label="Flow Editor" iconColor={NAV_COLORS.editor} />
  <NavItem href="/flow-assist" icon={WandSparkles} label="Flow Assist" iconColor={NAV_COLORS.flowAssist} />
  <NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} />
  <NavItem href="/kb-accelerator" icon={Lightbulb} label="KB Accelerator" iconColor={NAV_COLORS.kb} />

  {/* Insights group */}
  <div className="font-label text-[0.5625rem] uppercase tracking-[0.12em] text-[#5a6170] px-3 pt-3 pb-1">
    Insights
  </div>
  <NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} />
  <NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} />
</div>
```

**Collapsed layout** — update to include all 13 items (add Brain/FlowPilot and WandSparkles/Flow Assist, remove BotMessageSquare/AI Assistant):
```tsx
<div className="flex flex-col items-center px-1.5 py-3 space-y-1">
  <NavItem href="/" icon={LayoutGrid} label="Dashboard" iconColor={NAV_COLORS.dashboard} collapsed />
  <NavItem href="/sessions" icon={Clock} label="Sessions" badge={stats?.active_count || undefined} iconColor={NAV_COLORS.sessions} collapsed />
  <NavItem href="/trees" icon={Network} label="All Flows" matchPaths={['/trees', '/flows']} iconColor={NAV_COLORS.flows} collapsed />
  <NavItem href="/assistant" icon={Brain} label="FlowPilot" iconColor={NAV_COLORS.flowPilot} collapsed />
  <NavItem href="/scripts" icon={Code2} label="Script Library" iconColor={NAV_COLORS.scripts} collapsed />
  <NavItem href="/my-trees" icon={Wrench} label="Flow Editor" iconColor={NAV_COLORS.editor} collapsed />
  <NavItem href="/flow-assist" icon={WandSparkles} label="Flow Assist" iconColor={NAV_COLORS.flowAssist} collapsed />
  <NavItem href="/step-library" icon={Library} label="Step Library" iconColor={NAV_COLORS.stepLib} collapsed />
  <NavItem href="/kb-accelerator" icon={Lightbulb} label="KB Accelerator" iconColor={NAV_COLORS.kb} collapsed />
  <NavItem href="/shares" icon={FileOutput} label="Exports" iconColor={NAV_COLORS.exports} collapsed />
  <NavItem href="/analytics" icon={BarChart3} label="Analytics" iconColor={NAV_COLORS.analytics} collapsed />
  <NavItem href="/guides" icon={BookOpen} label="User Guides" iconColor={NAV_COLORS.guides} collapsed />
  <NavItem href="/feedback" icon={MessageSquareText} label="Feedback" iconColor={NAV_COLORS.feedback} collapsed />
</div>
```

- [ ] **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 (
    <div className="p-6">
      <div className="mb-6">
        <h1 className="font-heading text-2xl font-bold tracking-tight text-foreground">
          <span className="inline-flex items-center gap-2">
            <WandSparkles size={24} style={{ color: '#f472b6' }} />
            Flow Assist
          </span>
        </h1>
        <p className="mt-1 text-sm text-muted-foreground">
          Build flows from natural language — describe what you need and Flow Assist will generate the decision tree or procedural steps.
        </p>
      </div>

      <div className="glass-card-static p-8 text-center">
        <WandSparkles size={40} className="mx-auto mb-4 text-muted-foreground" />
        <h2 className="font-heading text-lg font-semibold text-foreground mb-2">
          Coming Soon
        </h2>
        <p className="text-sm text-muted-foreground max-w-md mx-auto">
          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.
        </p>
      </div>
    </div>
  )
}
```

- [ ] **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 <changed-files>
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 |
