diff --git a/server/src/__tests__/project-icon-persistence.test.ts b/server/src/__tests__/project-icon-persistence.test.ts index e19bf7c1316..688072ad5f4 100644 --- a/server/src/__tests__/project-icon-persistence.test.ts +++ b/server/src/__tests__/project-icon-persistence.test.ts @@ -68,4 +68,28 @@ describeEmbeddedPostgres("project icon persistence", () => { const fetched = await projects.getById(created.id); expect(fetched?.icon).toBeNull(); }); + + // PAP-71: new projects must NOT auto-assign a color — they stay neutral gray + // (color = null) unless an explicit color is supplied on create. + it("defaults color to null when none is provided (no auto-assign)", async () => { + companyId = await seedCompany(); + const projects = projectService(db); + + const created = await projects.create(companyId, { name: "Gray" }); + expect(created.color).toBeNull(); + + const fetched = await projects.getById(created.id); + expect(fetched?.color).toBeNull(); + }); + + it("still persists an explicit color when one is supplied", async () => { + companyId = await seedCompany(); + const projects = projectService(db); + + const created = await projects.create(companyId, { name: "Blue", color: "#3b82f6" }); + expect(created.color).toBe("#3b82f6"); + + const fetched = await projects.getById(created.id); + expect(fetched?.color).toBe("#3b82f6"); + }); }); diff --git a/server/src/services/projects.ts b/server/src/services/projects.ts index b05b5032ffb..7cb38f676d2 100644 --- a/server/src/services/projects.ts +++ b/server/src/services/projects.ts @@ -12,7 +12,6 @@ import { workspaceRuntimeServices, } from "@paperclipai/db"; import { - PROJECT_COLORS, deriveProjectUrlKey, hasNonAsciiContent, isUuidLike, @@ -549,13 +548,8 @@ export function projectService(db: Db) { const { goalIds: inputGoalIds, ...projectData } = data; const ids = resolveGoalIds({ goalIds: inputGoalIds, goalId: projectData.goalId }); - // Auto-assign a color from the palette if none provided - if (!projectData.color) { - const existing = await db.select({ color: projects.color }).from(projects).where(eq(projects.companyId, companyId)); - const usedColors = new Set(existing.map((r) => r.color).filter(Boolean)); - const nextColor = PROJECT_COLORS.find((c) => !usedColors.has(c)) ?? PROJECT_COLORS[existing.length % PROJECT_COLORS.length]; - projectData.color = nextColor; - } + // Note: color is intentionally NOT auto-assigned. New projects default to + // `color = null` (neutral gray) unless an explicit color is supplied. See PAP-68. const existingProjects = await db .select({ id: projects.id, name: projects.name }) diff --git a/ui/src/components/NewProjectDialog.tsx b/ui/src/components/NewProjectDialog.tsx index ad4aee14dd3..2d732f69d3f 100644 --- a/ui/src/components/NewProjectDialog.tsx +++ b/ui/src/components/NewProjectDialog.tsx @@ -33,7 +33,6 @@ import { TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; -import { PROJECT_COLORS } from "@paperclipai/shared"; import { cn } from "../lib/utils"; import { MarkdownEditor, type MarkdownEditorRef, type MentionOption } from "./MarkdownEditor"; import { StatusBadge } from "./StatusBadge"; @@ -165,7 +164,7 @@ export function NewProjectDialog() { name: name.trim(), description: description.trim() || undefined, status, - color: PROJECT_COLORS[Math.floor(Math.random() * PROJECT_COLORS.length)], + // No color is sent — new projects persist color = null (neutral gray). See PAP-68. ...(goalIds.length > 0 ? { goalIds } : {}), ...(targetDate ? { targetDate } : {}), });