Add project CRUD API (GET/POST/PATCH/DELETE) with ownership checks and transactional delete. Add diagram list filtering by projectId and unorganized query params with typed Zod query schema for Hono RPC type safety. Create DiagramSidebar with Projects tree (expand/collapse, inline rename) and Recent tab. Add project picker to CreateDiagramDialog. Includes 15 schema validation tests (107 total passing). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
19 KiB
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
-
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.
-
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.
-
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.
-
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).
-
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
- Task 1: Create project CRUD API endpoints (AC: #1, #4)
- 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 - 1.2: Add Zod validation schemas for create/update project inputs
- 1.3: GET /projects returns user's projects ordered by sortOrder
- 1.4: DELETE /projects/:id sets
projectId = nullon all diagrams in that project (moves to Unorganized), then deletes the project row - 1.5: Register project routes in
packages/api/src/index.ts
- 1.1: Add project CRUD routes to
- Task 2: Add project filter to diagram list API (AC: #2)
- 2.1: Add optional
projectIdquery param to GET /diagrams — when present, filter diagrams by projectId - 2.2: Add a GET /diagrams?unorganized=true filter for diagrams with null projectId
- 2.1: Add optional
- Task 3: Update CreateDiagramDialog to support optional project selection (AC: #3)
- 3.1: Fetch user's projects and display a project dropdown/select in the create modal
- 3.2: Pass selected projectId to the POST /diagrams API call
- Task 4: Create sidebar with Projects tree view and Recent tab (AC: #1, #2, #5)
- 4.1: Create
apps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx— client component with two tabs: "Projects" and "Recent" - 4.2: Projects tab: tree view listing projects with expandable diagram items underneath. "All Diagrams" and "Unorganized" pseudo-entries at top
- 4.3: Recent tab: flat list of all diagrams sorted by updatedAt desc, showing title + type icon + relative time
- 4.4: "New Project" button in sidebar header area
- 4.5: Integrate sidebar into dashboard layout — added "Diagrams" to DashboardSidebar menu config + DiagramSidebar as panel in diagrams page
- 4.1: Create
- Task 5: Implement project context menu — rename and delete (AC: #4)
- 5.1: Kebab menu (DropdownMenu) on project name with Rename / Delete options
- 5.2: Rename: inline editable field — saves on blur/Enter via PATCH /projects/:id
- 5.3: Delete: AlertDialog confirmation warning that diagrams will be moved to Unorganized — calls DELETE /projects/:id
- 5.4: React Query invalidation on mutations
- Task 6: Tests (AC: all)
- 6.1: API schema validation tests for project create/update schemas (15 tests)
- 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:
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.
// 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:
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:
.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,SidebarRailfrom@turbostarter/ui-web/sidebar
UI components to use:
Tabs,TabsList,TabsTrigger,TabsContentfrom@turbostarter/ui-web/tabsContextMenu,ContextMenuTrigger,ContextMenuContent,ContextMenuItemfrom@turbostarter/ui-web/context-menu(for right-click on projects)Inputfor inline renameAlertDialogfor delete confirmationIcons.FolderOpen,Icons.Clock,Icons.Plus,Icons.MoreHorizontal,Icons.Pencil,Icons.Trash2Select/SelectTrigger/SelectContent/SelectItemfrom@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
Selectdropdown below the title input: "Project (optional)" with project names + "No project" default - Pass
projectIdto 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
timeAgofrom 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 APIpackages/api/src/index.ts— MODIFIED: add projectRouter registrationpackages/api/src/modules/diagram/router.ts— MODIFIED: add projectId/unorganized query filtersapps/web/src/modules/diagram/components/sidebar/DiagramSidebar.tsx— NEW: client sidebar with tabsapps/web/src/modules/diagram/components/sidebar/ProjectTree.tsx— NEW: project tree viewapps/web/src/modules/diagram/components/sidebar/RecentList.tsx— NEW: recent diagrams listapps/web/src/modules/diagram/components/sidebar/ProjectContextMenu.tsx— NEW: rename/delete context menuapps/web/src/modules/diagram/components/CreateDiagramDialog.tsx— MODIFIED: add project selectapps/web/src/app/[locale]/dashboard/(user)/diagrams/page.tsx— MODIFIED: integrate DiagramSidebarapps/web/src/app/[locale]/dashboard/(user)/layout.tsx— MODIFIED: add "Diagrams" to sidebar menuapps/web/src/config/paths.ts— Already hasdiagramspath (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 usingtext().$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-kityet — drag-and-drop is Story 1.4. This story only covers Projects tree + Recent list + context menus - DO NOT modify the
DashboardSidebarcomponent itself — only modify themenuconfig 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-fnsis NOT available — use the inlinetimeAgo()helper already in DiagramCard.tsx- Drizzle
createInsertSchemawithcoerce: truemakes most fields optional — be careful with test expectations - The Hono RPC client returns JSON-serialized dates as strings, not Date objects — use
DiagramResponsetype from DiagramCard.tsx for frontend types toastfromsonnerfor error feedbackIcons.AlertTriangleis available for error states- DiagramCard already exports
diagramTypeConfigfor reuse in the sidebar Recent list pathsConfig.dashboard.user.diagram(id)for navigation to editorpathsConfig.dashboard.user.diagramsfor 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 filteringapps/web/src/modules/diagram/components/CreateDiagramDialog.tsx— add project pickerapps/web/src/app/[locale]/dashboard/(user)/diagrams/page.tsx— integrate sidebarapps/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 patternsda3368f docs: add autonomous setup prompt for AI agents06f3722 docs: add developer setup instructions to README3527e73 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.tsschema - [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.tswith GET/POST/PATCH/DELETE. Ownership checks on PATCH/DELETE. DELETE nullifies diagram projectIds before removing project row. - Task 2: Modified
router.tsGET /diagrams to acceptprojectIdandunorganizedquery params for filtering. - Task 3: Updated
CreateDiagramDialog.tsxwith 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 /projectspackages/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 creationapps/web/src/modules/diagram/components/sidebar/ProjectTree.tsx— NEW: project tree with expand/collapse, inline renameapps/web/src/modules/diagram/components/sidebar/RecentList.tsx— NEW: recent diagrams flat listapps/web/src/modules/diagram/components/sidebar/ProjectContextMenu.tsx— NEW: dropdown menu with rename/deleteapps/web/src/modules/diagram/components/CreateDiagramDialog.tsx— MODIFIED: added project picker Selectapps/web/src/app/[locale]/dashboard/(user)/diagrams/page.tsx— MODIFIED: integrated DiagramSidebar, filtered queriesapps/web/src/app/[locale]/dashboard/(user)/layout.tsx— MODIFIED: added Diagrams to sidebar menupackages/ui/web/src/components/icons.tsx— MODIFIED: added FolderOpen, Inbox, Trash2 iconspackages/api/tests/diagram/project-schema.test.ts— NEW: 15 project schema validation tests