# Story 1.2: Organize Diagrams into Projects

Status: done

## Story

As a user,
I want to group my diagrams into projects (folders),
so that I can organize related diagrams together and navigate my workspace efficiently.

## Acceptance Criteria

1. **Given** I am on the dashboard, **When** I click "New Project" in the sidebar, **Then** a new project is created with an editable name field, **And** the project appears in the sidebar Projects tree view.

2. **Given** I have projects in my sidebar, **When** I click on a project name, **Then** the main content area filters to show only diagrams in that project, **And** the project is highlighted in the sidebar tree.

3. **Given** I am creating a new diagram, **When** I optionally select a project in the creation modal, **Then** the diagram is created inside that project, **And** it appears under the project node in the sidebar tree.

4. **Given** I have projects in the sidebar, **When** I right-click a project, **Then** I see options to Rename and Delete the project, **And** deleting a project moves its diagrams to "Unorganized" (not deleted).

5. **Given** I am on the dashboard, **When** I switch to the "Recent" tab in the sidebar, **Then** I see all diagrams across all projects sorted by last interaction timestamp.

## Tasks / Subtasks

- [x] Task 1: Create project CRUD API endpoints (AC: #1, #4)
  - [x] 1.1: Add project CRUD routes to `packages/api/src/modules/diagram/project-router.ts`: GET /projects, POST /projects, PATCH /projects/:id, DELETE /projects/:id
  - [x] 1.2: Add Zod validation schemas for create/update project inputs
  - [x] 1.3: GET /projects returns user's projects ordered by sortOrder
  - [x] 1.4: DELETE /projects/:id sets `projectId = null` on all diagrams in that project (moves to Unorganized), then deletes the project row
  - [x] 1.5: Register project routes in `packages/api/src/index.ts`
- [x] Task 2: Add project filter to diagram list API (AC: #2)
  - [x] 2.1: Add optional `projectId` query param to GET /diagrams — when present, filter diagrams by projectId
  - [x] 2.2: Add a GET /diagrams?unorganized=true filter for diagrams with null projectId
- [x] Task 3: Update CreateDiagramDialog to support optional project selection (AC: #3)
  - [x] 3.1: Fetch user's projects and display a project dropdown/select in the create modal
  - [x] 3.2: Pass selected projectId to the POST /diagrams API call
- [x] Task 4: Create sidebar with Projects tree view and Recent tab (AC: #1, #2, #5)
  - [x] 4.1: Create `apps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx` — client component with two tabs: "Projects" and "Recent"
  - [x] 4.2: Projects tab: tree view listing projects with expandable diagram items underneath. "All Diagrams" and "Unorganized" pseudo-entries at top
  - [x] 4.3: Recent tab: flat list of all diagrams sorted by updatedAt desc, showing title + type icon + relative time
  - [x] 4.4: "New Project" button in sidebar header area
  - [x] 4.5: Integrate sidebar into dashboard layout — added "Diagrams" to DashboardSidebar menu config + DiagramSidebar as panel in diagrams page
- [x] Task 5: Implement project context menu — rename and delete (AC: #4)
  - [x] 5.1: Kebab menu (DropdownMenu) on project name with Rename / Delete options
  - [x] 5.2: Rename: inline editable field — saves on blur/Enter via PATCH /projects/:id
  - [x] 5.3: Delete: AlertDialog confirmation warning that diagrams will be moved to Unorganized — calls DELETE /projects/:id
  - [x] 5.4: React Query invalidation on mutations
- [x] Task 6: Tests (AC: all)
  - [x] 6.1: API schema validation tests for project create/update schemas (15 tests)
  - [x] 6.2: Schema-level validation tests (no DB required)

## Dev Notes

### Project Database Schema — Already Created in Story 1.1

The `project` table already exists in `packages/db/src/schema/diagram.ts`:

```typescript
export const project = pgTable("project", {
  id: text().primaryKey().notNull().$defaultFn(generateId),
  name: text().notNull(),
  userId: text().references(() => user.id, { onDelete: "cascade" }).notNull(),
  sortOrder: integer().default(0),
  createdAt: timestamp().defaultNow(),
  updatedAt: timestamp().$onUpdate(() => new Date()),
});
```

Zod schemas and types are also already exported: `insertProjectSchema`, `selectProjectSchema`, `updateProjectSchema`, `InsertProject`, `SelectProject`, `UpdateProject`.

**NO schema changes or migrations needed for this story.**

### API — Project CRUD Routes

Add project routes. Two options:

**Option A (recommended): Add to existing `packages/api/src/modules/diagram/router.ts`** — since projects are tightly coupled to diagrams (same domain). Create a separate `projectRouter` in a new file `packages/api/src/modules/diagram/project-router.ts` and register it alongside the diagram router.

```typescript
// packages/api/src/modules/diagram/project-router.ts
import { Hono } from "hono";
import { eq, asc } from "drizzle-orm";
import { z } from "zod";
import { project, diagram } from "@turbostarter/db/schema";
import { db } from "@turbostarter/db/server";
import { HttpStatusCode } from "@turbostarter/shared/constants";
import { HttpException } from "@turbostarter/shared/utils";
import { enforceAuth, validate } from "../../middleware";

const createProjectSchema = z.object({
  name: z.string().min(1).max(100),
});

const updateProjectSchema = z.object({
  name: z.string().min(1).max(100).optional(),
  sortOrder: z.number().int().optional(),
});

export const projectRouter = new Hono()
  .get("/", enforceAuth, async (c) => {
    const projects = await db.select().from(project)
      .where(eq(project.userId, c.var.user.id))
      .orderBy(asc(project.sortOrder));
    return c.json({ data: projects });
  })
  .post("/", enforceAuth, validate("json", createProjectSchema), async (c) => {
    const input = c.req.valid("json");
    const [created] = await db.insert(project).values({
      ...input, userId: c.var.user.id,
    }).returning();
    return c.json({ data: created });
  })
  .patch("/:id", enforceAuth, validate("json", updateProjectSchema), async (c) => {
    const [existing] = await db.select().from(project)
      .where(and(eq(project.id, c.req.param("id")), eq(project.userId, c.var.user.id)));
    if (!existing) throw new HttpException(HttpStatusCode.NOT_FOUND, { code: "error.notFound" });
    const [updated] = await db.update(project)
      .set(c.req.valid("json"))
      .where(eq(project.id, c.req.param("id")))
      .returning();
    return c.json({ data: updated });
  })
  .delete("/:id", enforceAuth, async (c) => {
    const [existing] = await db.select().from(project)
      .where(and(eq(project.id, c.req.param("id")), eq(project.userId, c.var.user.id)));
    if (!existing) throw new HttpException(HttpStatusCode.NOT_FOUND, { code: "error.notFound" });
    // Move diagrams to Unorganized (null projectId)
    await db.update(diagram).set({ projectId: null })
      .where(eq(diagram.projectId, c.req.param("id")));
    // Delete the project
    await db.delete(project).where(eq(project.id, c.req.param("id")));
    return c.json({ data: { success: true } });
  });
```

Register in `packages/api/src/index.ts`:
```typescript
import { projectRouter } from "./modules/diagram/project-router";
// In appRouter chain:
.route("/projects", projectRouter)
```

### Diagram List API — Add Project Filter

Modify GET /diagrams in `packages/api/src/modules/diagram/router.ts` to accept optional query params:

```typescript
.get("/", enforceAuth, async (c) => {
  const projectId = c.req.query("projectId");
  const unorganized = c.req.query("unorganized");

  const conditions = [
    eq(diagram.userId, c.var.user.id),
    isNull(diagram.deletedAt),
  ];

  if (projectId) {
    conditions.push(eq(diagram.projectId, projectId));
  } else if (unorganized === "true") {
    conditions.push(isNull(diagram.projectId));
  }

  const diagrams = await db.select().from(diagram)
    .where(and(...conditions))
    .orderBy(desc(diagram.updatedAt));

  return c.json({ data: diagrams });
})
```

### Sidebar — DiagramSidebar Component

**Architecture reference:** The architecture doc specifies `apps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx`.

The existing `DashboardSidebar` is a **server component** that receives a static `menu` array of links. Story 1.2 needs a **client-side interactive sidebar** with:
- Two tabs: Projects (tree) and Recent (flat list)
- Dynamic data fetching (useQuery for projects + diagrams)
- Context menus
- Interactive state (expand/collapse project nodes)

**Approach:** Add a "Diagrams" link to the existing DashboardSidebar menu that navigates to `/dashboard/diagrams`. Then the diagrams page itself renders the `DiagramSidebar` as a secondary sidebar or panel within the page content area. Alternatively, extend the layout.

**Recommended:** Create `DiagramSidebar` as a client component rendered inside the diagrams page layout. This avoids modifying the server-side DashboardSidebar pattern. Use shadcn/ui `Tabs` for Projects/Recent switching.

**TurboStarter sidebar components available:**
- `Sidebar`, `SidebarContent`, `SidebarHeader`, `SidebarGroup`, `SidebarGroupLabel`, `SidebarMenu`, `SidebarMenuItem`, `SidebarMenuButton`, `SidebarRail` from `@turbostarter/ui-web/sidebar`

**UI components to use:**
- `Tabs`, `TabsList`, `TabsTrigger`, `TabsContent` from `@turbostarter/ui-web/tabs`
- `ContextMenu`, `ContextMenuTrigger`, `ContextMenuContent`, `ContextMenuItem` from `@turbostarter/ui-web/context-menu` (for right-click on projects)
- `Input` for inline rename
- `AlertDialog` for delete confirmation
- `Icons.FolderOpen`, `Icons.Clock`, `Icons.Plus`, `Icons.MoreHorizontal`, `Icons.Pencil`, `Icons.Trash2`
- `Select` / `SelectTrigger` / `SelectContent` / `SelectItem` from `@turbostarter/ui-web/select` (for project picker in CreateDiagramDialog)

### CreateDiagramDialog — Add Project Picker

Modify `apps/web/src/modules/diagram/components/CreateDiagramDialog.tsx`:
- Fetch projects via `useQuery({ queryKey: ["projects"] })`
- Add a `Select` dropdown below the title input: "Project (optional)" with project names + "No project" default
- Pass `projectId` to the create mutation

### Recent Tab

The Recent tab shows diagrams across all projects sorted by `updatedAt` desc. Use the existing `GET /diagrams` endpoint (no projectId filter). Each item shows:
- Diagram title
- Type icon with accent color
- Relative timestamp (reuse `timeAgo` from DiagramCard)

Clicking a diagram in Recent navigates to `/dashboard/diagram/[id]`.

### Sidebar Integration with Dashboard Layout

The `DiagramSidebar` should be a **panel inside the diagrams page**, NOT replacing the main dashboard sidebar. The dashboard already has a top-level sidebar (DashboardSidebar) with navigation links. The `DiagramSidebar` is a secondary navigation within the diagrams content area.

**Layout approach:** In the diagrams page, render a two-column layout:
```
[DashboardSidebar (existing)] | [DiagramSidebar (new)] | [DiagramGrid (existing)]
```

Add a sidebar menu item "Diagrams" to the dashboard layout's menu config pointing to `/dashboard/diagrams`.

### Project Structure Notes

- `packages/api/src/modules/diagram/project-router.ts` — NEW: project CRUD API
- `packages/api/src/index.ts` — MODIFIED: add projectRouter registration
- `packages/api/src/modules/diagram/router.ts` — MODIFIED: add projectId/unorganized query filters
- `apps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx` — NEW: client sidebar with tabs
- `apps/web/src/modules/diagram/components/sidebar/ProjectTree.tsx` — NEW: project tree view
- `apps/web/src/modules/diagram/components/sidebar/RecentList.tsx` — NEW: recent diagrams list
- `apps/web/src/modules/diagram/components/sidebar/ProjectContextMenu.tsx` — NEW: rename/delete context menu
- `apps/web/src/modules/diagram/components/CreateDiagramDialog.tsx` — MODIFIED: add project select
- `apps/web/src/app/[locale]/dashboard/(user)/diagrams/page.tsx` — MODIFIED: integrate DiagramSidebar
- `apps/web/src/app/[locale]/dashboard/(user)/layout.tsx` — MODIFIED: add "Diagrams" to sidebar menu
- `apps/web/src/config/paths.ts` — Already has `diagrams` path (no change needed)
- `packages/api/tests/diagram/project-schema.test.ts` — NEW: project API schema tests

### Anti-Patterns to Avoid

- **NEVER** use `uuid()` column type — already using `text().$defaultFn(generateId)` ✓
- **NEVER** hard-delete diagrams when deleting a project — set `projectId = null` (move to Unorganized)
- **NEVER** put business logic in API routers — keep DELETE handler simple: nullify FKs → delete row
- **NEVER** co-locate feature code in route directories — sidebar goes in `~/modules/diagram/components/sidebar/`
- **DO NOT** install `@dnd-kit` yet — drag-and-drop is Story 1.4. This story only covers Projects tree + Recent list + context menus
- **DO NOT** modify the `DashboardSidebar` component itself — only modify the `menu` config in the layout file
- **DO NOT** change the project or diagram DB schema — both tables already exist from Story 1.1

### Previous Story Intelligence (Story 1.1)

**Key learnings from Story 1.1 implementation:**
- `date-fns` is NOT available — use the inline `timeAgo()` helper already in DiagramCard.tsx
- Drizzle `createInsertSchema` with `coerce: true` makes most fields optional — be careful with test expectations
- The Hono RPC client returns JSON-serialized dates as strings, not Date objects — use `DiagramResponse` type from DiagramCard.tsx for frontend types
- `toast` from `sonner` for error feedback
- `Icons.AlertTriangle` is available for error states
- DiagramCard already exports `diagramTypeConfig` for reuse in the sidebar Recent list
- `pathsConfig.dashboard.user.diagram(id)` for navigation to editor
- `pathsConfig.dashboard.user.diagrams` for the diagrams list page
- All 92 existing tests pass — don't break them

**Files from Story 1.1 that this story modifies:**
- `packages/api/src/modules/diagram/router.ts` — add query param filtering
- `apps/web/src/modules/diagram/components/CreateDiagramDialog.tsx` — add project picker
- `apps/web/src/app/[locale]/dashboard/(user)/diagrams/page.tsx` — integrate sidebar
- `apps/web/src/app/[locale]/dashboard/(user)/layout.tsx` — add Diagrams menu item

### Git Intelligence

Recent commits:
- `392da38 feat: implement Story 1.1 — create and view diagrams` — 20 files, established all diagram patterns
- `da3368f docs: add autonomous setup prompt for AI agents`
- `06f3722 docs: add developer setup instructions to README`
- `3527e73 feat: turbostarter boilerplate` — initial project

### References

- [Source: _bmad-output/planning-artifacts/epics.md#Story 1.2] — Full AC and technical notes
- [Source: _bmad-output/planning-artifacts/architecture.md] — Projects (FR55-56) → `diagrams/` API + `sidebar/` frontend + `project.ts` schema
- [Source: _bmad-output/planning-artifacts/architecture.md] — Source tree: `apps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx`
- [Source: _bmad-output/implementation-artifacts/1-1-create-and-view-diagrams.md] — Previous story with all file patterns and learnings
- [Source: _bmad-output/project-context.md] — All critical implementation rules
- [Source: apps/web/src/app/[locale]/dashboard/(user)/layout.tsx] — Dashboard sidebar menu config pattern
- [Source: apps/web/src/modules/common/layout/dashboard/sidebar.tsx] — DashboardSidebar component (server component, menu-driven)

## Dev Agent Record

### Agent Model Used
Claude Opus 4.6 (team-based: backend + frontend + tests agents in parallel)

### Debug Log References
- TypeScript: 0 errors (both API and web packages)
- Tests: 107 pass (92 existing + 15 new project schema tests)
- Frontend agent adapted ContextMenu → DropdownMenu (kebab menu, more discoverable UX)
- Frontend agent added FolderOpen, Inbox, Trash2 icons to icons.tsx

### Completion Notes List
- Task 1: Created `project-router.ts` with GET/POST/PATCH/DELETE. Ownership checks on PATCH/DELETE. DELETE nullifies diagram projectIds before removing project row.
- Task 2: Modified `router.ts` GET /diagrams to accept `projectId` and `unorganized` query params for filtering.
- Task 3: Updated `CreateDiagramDialog.tsx` with project Select dropdown, fetches projects via useQuery, passes projectId to create mutation.
- Task 4: Created 4 sidebar components: `DiagramSidebar.tsx` (tabs + new project form), `ProjectTree.tsx` (All/Unorganized/projects tree with expand/collapse), `RecentList.tsx` (flat list with timeAgo), `ProjectContextMenu.tsx` (dropdown menu with rename/delete + AlertDialog confirmation).
- Task 5: ProjectContextMenu uses DropdownMenu (kebab) instead of ContextMenu (right-click) — more accessible and discoverable. Rename state lifted to ProjectTree for inline editing.
- Task 6: Created 15 project schema validation tests (6 create + 9 update) in `project-schema.test.ts`.
- Layout: Added "diagrams" item to dashboard sidebar menu config.
- Integration: DiagramSidebar integrated as left panel in diagrams page with filtered diagram query.

### Senior Developer Review (AI)
**Reviewer:** Claude Opus 4.6 (adversarial code review)
**Issues Found:** 2 HIGH, 4 MEDIUM, 1 LOW — all 6 HIGH/MEDIUM fixed

| # | Severity | Issue | File | Fix |
|---|----------|-------|------|-----|
| 1 | HIGH | DiagramsPage used raw `fetch` instead of Hono RPC client | `diagrams/page.tsx` | Switched to `api.diagrams.$get({ query })` with typed `listDiagramsQuerySchema` |
| 2 | HIGH | DELETE /projects/:id not transactional | `project-router.ts` | Wrapped in `db.transaction()` |
| 3 | MEDIUM | Unused `Input` import | `ProjectContextMenu.tsx` | Removed |
| 4 | MEDIUM | PATCH accepts empty body | `project-router.ts` | Added `.refine()` requiring at least one field |
| 5 | MEDIUM | Duplicate icon for diagrams/demos menu | `layout.tsx` | Changed diagrams to `Icons.GitBranch` |
| 6 | MEDIUM | Duplicate `timeAgo` function | `RecentList.tsx` | Exported from DiagramCard, imported in RecentList |
| 7 | LOW | Raw fetch doesn't check `res.ok` | `diagrams/page.tsx` | Resolved by fix #1 (RPC client handles this) |

### File List
- `packages/api/src/modules/diagram/project-router.ts` — NEW: project CRUD API (GET/POST/PATCH/DELETE)
- `packages/api/src/index.ts` — MODIFIED: registered projectRouter at /projects
- `packages/api/src/modules/diagram/router.ts` — MODIFIED: added projectId/unorganized query filters to GET /
- `apps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx` — NEW: client sidebar with tabs + project creation
- `apps/web/src/modules/diagram/components/sidebar/ProjectTree.tsx` — NEW: project tree with expand/collapse, inline rename
- `apps/web/src/modules/diagram/components/sidebar/RecentList.tsx` — NEW: recent diagrams flat list
- `apps/web/src/modules/diagram/components/sidebar/ProjectContextMenu.tsx` — NEW: dropdown menu with rename/delete
- `apps/web/src/modules/diagram/components/CreateDiagramDialog.tsx` — MODIFIED: added project picker Select
- `apps/web/src/app/[locale]/dashboard/(user)/diagrams/page.tsx` — MODIFIED: integrated DiagramSidebar, filtered queries
- `apps/web/src/app/[locale]/dashboard/(user)/layout.tsx` — MODIFIED: added Diagrams to sidebar menu
- `packages/ui/web/src/components/icons.tsx` — MODIFIED: added FolderOpen, Inbox, Trash2 icons
- `packages/api/tests/diagram/project-schema.test.ts` — NEW: 15 project schema validation tests
