Skip to content

feat: Redesign sidebar to consolidate threads under projects (t3code pattern) #918

@ryaneggz

Description

@ryaneggz

Metadata

IMPORTANT: The very first step should ALWAYS be validating this metadata section to maintain a CLEAN development workflow.

pull_request_title: "FROM feat/[issue#]-sidebar-project-threads TO development"
branch: "feat/[issue#]-sidebar-project-threads"
worktree_path: "$WORKSPACE/.worktrees/feat-[issue#]"

User Stories

  • As a user with multiple projects, I want threads nested under their parent project in the sidebar so that I can see all project activity without navigating to a separate page.
  • As a user managing threads, I want inline "Create new thread" buttons per project so that I can start a new conversation in the correct project context immediately.
  • As a user with many threads, I want per-project "Show more"/"Show less" pagination so that the sidebar stays manageable without hiding my work.

Summary

Redesign the sidebar to consolidate threads under their parent projects in a collapsible tree, matching the t3code pattern observed at localhost:3773. Currently, projects and threads are separate sidebar sections — project threads are only visible on the project page. The new design nests threads directly under each project with lazy-loaded pagination, inline thread creation, and orphan threads in a separate bottom section.

Visual Reference

t3code sidebar (reference design):

Sidebar
  "Projects" header + "Add project" button
  Project "orchestra" (collapsible) + [+ New thread]
    Thread "Implement Agent-Browser..." [Plan Ready] 20d ago
    Thread "BUG: Starting New Chat..." [Plan Ready] 20d ago
    "Show more" / "Show less"
  Project "ryaneggz" (collapsible) + [+ New thread]
    Thread "New thread" 10d ago
  ---separator---
  "New thread" section (orphan threads)
  Settings

Sidebar state transitions:

  New User              First Project          Active User
  (empty)               Created                (multiple projects)
+---------------+   +------------------+   +------------------------+
| v Projects [+]|   | v Projects    [+]|   | v Projects          [+]|
|   No projects |   |   v MyProj  [+t] |   |   v Proj-A     [+t]   |
|   yet.        |   |     (no threads) |   |     Thread 1    2m ago |
|   [+ Create]  |   |                  |   |     Thread 2    1h ago |
|               |   | > Threads (3)    |   |     [Show more]        |
| > Threads (0) |   |                  |   |   > Proj-B     [+t]   |
|               |   | Settings         |   |     Thread 3    3h ago |
| Settings      |   +------------------+   |   > Proj-C     [+t]   |
+---------------+                          |                        |
                                           | > Threads (2)          |
                                           |                        |
                                           | Settings               |
                                           +------------------------+

User flow:

                         +------------------+
                         |   User opens app |
                         +--------+---------+
                                  |
                         +--------v---------+
                         | Sidebar renders  |
                         | with Projects    |
                         | expanded         |
                         +--------+---------+
                                  |
                    +-------------+-------------+
                    |                           |
           +--------v---------+       +--------v---------+
           | Has projects?    |       | Has orphan       |
           | NO               |       | threads? YES     |
           +--------+---------+       +--------+---------+
                    |                           |
           +--------v---------+       +--------v---------+
           | Show empty state |       | Show collapsed   |
           | "No projects yet"|       | "Threads (N)"    |
           | [+ Create Project|       | section at bottom|
           +--------+---------+       +--------+---------+
                    |
           +--------v---------+
           | User clicks      |
           | "Create Project" |
           +--------+---------+
                    |
           +--------v---------+
           | Project appears  |
           | in sidebar tree  |
           | (expanded, empty)|
           +--------+---------+
                    |
        +-----------+-----------+
        |                       |
+-------v--------+    +--------v---------+
| User clicks    |    | User clicks      |
| [+ New thread] |    | project chevron  |
| on project     |    | to collapse      |
+-------+--------+    +--------+---------+
        |                       |
+-------v--------+    +--------v---------+
| Navigate to    |    | Threads hidden   |
| /p/:id with    |    | State saved to   |
| project_id set |    | localStorage     |
| in metadata    |    +------------------+
+-------+--------+
        |
+-------v--------+
| User sends     |
| first message  |
+-------+--------+
        |
+-------v-----------------------+
| Thread appears nested under   |
| project in sidebar            |
| (compact: title + timestamp)  |
+-------+-----------------------+
        |
+-------v-----------------------+
| More threads accumulate...    |
| After 5 threads:              |
| [Show more] button appears    |
+-------------------------------+

Thread move flow:

+---------------------------+       +---------------------------+
| Thread in orphan section  |       | Thread under Project A    |
| User clicks [...] menu    |       | User clicks [...] menu    |
+------------+--------------+       +------------+--------------+
             |                                   |
+------------v--------------+       +------------v--------------+
| "Move to Project" submenu |       | "Move to Project" submenu |
| shows all projects        |       | shows all projects        |
+------------+--------------+       +------------+--------------+
             |                                   |
+------------v--------------+       +------------v--------------+
| User selects Project B    |       | User selects Project C    |
+------------+--------------+       +------------+--------------+
             |                                   |
+------------v---------------------------------v-+
| Sync both states simultaneously:              |
|  - Remove from source (orphan or Project A)   |
|  - Add to destination (Project B or C)        |
|  - No flicker, no duplication                 |
+-----------------------------------------------+

Key Integration Points

File Function(s) Role
frontend/src/components/drawers/app-sidebar.tsx ProjectsCollapsibleGroup (L494-554), ThreadItem (L146-362), AppSidebar (L837-920) Main sidebar composition — replace ProjectsCollapsibleGroup, add compact variant to ThreadItem
frontend/src/lib/services/threadService.ts searchThreadsByProject() (L291) Existing API for fetching threads by project — no changes needed
frontend/src/components/ui/sidebar.tsx SidebarMenuSub, SidebarMenuSubItem, SidebarMenuSubButton (L701-752) Existing unused primitives for nested menu items — reuse for thread nesting
frontend/src/context/ProjectContext.tsx ProjectProvider May need thread mutation callbacks for cross-section sync
frontend/src/components/lists/ListProjectThreads.tsx searchThreadsByProject usage Reference pattern for project thread fetching

UI Integration Points

Component / Route Change Type Description
ProjectsCollapsibleGroup (app-sidebar.tsx) Replace New ProjectTreeGroup with nested threads per project
ThreadItem (app-sidebar.tsx) Modify Add compact and parentProjectId props for nested display
CollapsibleGroup threads section (app-sidebar.tsx) Modify Change to defaultOpen={false}, now secondary for orphan threads
ProjectTreeGroup (new) New component Collapsible project tree with nested thread items
useProjectThreads (new hook) New hook Per-project lazy thread fetching with pagination

Storage

  • Persistence layer: No new storage — threads already have project_id in LangGraph Store
  • Namespace / table: Existing thread metadata { project_id } relationship
  • Model pattern: useProjectThreads hook manages Map<projectId, ThreadsState> in React state; expanded project IDs persisted in localStorage

Architectural Decisions

  • No virtual scrolling for project trees: Per-project pagination caps at 5 items per page, keeping rendered DOM manageable. Virtual scrolling remains for the orphan threads section.
  • Lazy fetching: Project threads only fetched when user expands that project in the sidebar (not on mount), using existing searchThreadsByProject() API.
  • State sync: When a thread is moved between projects, both the orphan threads list (ChatContext) and the project threads map (useProjectThreads) are updated synchronously to prevent duplication.
  • Reuse existing primitives: SidebarMenuSub* components from sidebar.tsx (currently unused) provide the nested indentation and left-border styling.
  • New user empty state: Projects section expanded by default with "No projects yet" + "Create Project" CTA. Nudges project-first organization.

Documentation

  • t3code reference implementation at localhost:3773
  • Existing SidebarMenuSub components in sidebar.tsx (L701-752) designed for exactly this nested pattern

Development Setup

Dependencies

Service Address Notes
Frontend dev localhost:5173 or localhost:8030 npm run dev or npm run dev:claude
Backend API localhost:8000 Required for thread/project APIs
Redis localhost:6379 Docker container
Postgres localhost:5432 Docker container

Commands

cd frontend && npm run dev      # Dev server
cd frontend && npm run test     # Run tests
cd frontend && npm run lint     # Lint check

Wiki

IMPORTANT: Wiki lives in a separate repo. Changes to @wiki must be committed directly to the wiki repo, not the main project repo.


Design Principles

  • Simplicity is beauty, complexity is pain.
  • ALWAYS look at the current codebase first — achieve the goal in the least amount of changes.
  • Reuse ThreadItem with a compact variant rather than creating a duplicate component.
  • Reuse existing SidebarMenuSub* primitives rather than custom nesting CSS.
  • TDD-first: write tests before implementation.

Validation Tools

  • Load agent-browser skill with screenshots to validate E2E sidebar tree rendering

Acceptance Criteria

  • Implementation plan is thoroughly documented
  • Projects section is expanded by default with threads nested under each project
  • Clicking a project chevron expands/collapses its thread list (lazy fetch on first expand)
  • "Show more"/"Show less" paginates threads within a project (5 per page)
  • Inline "Create new thread" button per project navigates to /p/:projectId with correct metadata
  • Moving a thread between projects updates both sidebar sections without duplication or flicker
  • Orphan threads (no project_id) still appear in bottom "Threads" section (collapsed by default)
  • Mobile sidebar (Sheet) works correctly with nested project tree
  • Expanded project state persists across page reloads via localStorage
  • New user (no projects) sees empty state with "No projects yet" message and "Create Project" CTA
  • All previous & new tests pass, validated using agent-browser CLI
  • New code follows existing repo/service/route patterns (e.g., BaseRepo, ServiceContext)
  • No new dependencies added beyond what's already in the project (or justified in PR description)
  • Related API and user documentation updated in the wiki repo (committed directly to wiki repo)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions