Information Architecture + project/agent visual refresh (experimental) (#7543)

## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI is the control surface for issues, projects, agents,
goals, workspaces, and operator settings.
> - The existing navigation and list surfaces make several
high-frequency workflows feel harder to scan than they should,
especially around projects and agents.
> - The product direction is to improve those surfaces without breaking
the existing route model or forcing a new IA on every operator at once.
> - This pull request now keeps the dependent IA, project identity, and
agent-list visual refresh work together while the Issue-to-Task copy
migration is split into #7651.
> - The benefit is a clearer left nav, better project identity, denser
agent/project list rows, and brand-aligned status treatment while
preserving the classic default experience behind a flag.

## Linked Issues or Issue Description

Refs #7645
Refs #7651

Internal planning/work references: PAP-53, PAP-56, PAP-58, PAP-59,
PAP-60, PAP-61, PAP-68, PAP-69, PAP-70, PAP-71, PAP-72, PAP-75, PAP-76,
PAP-80, PAP-85, PAP-86, PAP-87, PAP-88, PAP-89.

## What Changed

- Adds `enableStreamlinedLeftNavigation`, defaulting off, and gates
sidebar presentation so classic navigation remains the default.
- Adds project icon persistence, validation, portability, picker UI, and
`ProjectTile` rendering while defaulting new projects to neutral gray.
- Adds projects-list task-count and budget summary data with focused
server/shared/UI coverage.
- Refreshes agent list rows, row actions, active/recent sidebar
behavior, and status capsule/chip styling for the approved brand state
system.
- Removes the placeholder Conference room and Artifacts nav/routes from
the finalized experimental nav direction.
- Removes `pnpm-lock.yaml` and the Issue-to-Task copy migration from
this PR diff; the copy migration now lives in #7651.

## Verification

- Existing branch verification from the authored commits: UI typecheck,
targeted unit tests, and light/dark visual checks for `/agents`, agent
detail, and design-guide status states.
- Maintainer cleanup verification on `75e34e5`: `git diff --check
origin/master...HEAD` passed, the `design/` diff is empty, and the PR
diff is 61 files, below Greptile's 100-file review limit.
- `pnpm --filter @paperclipai/ui build` passed.
- `NODE_ENV=test pnpm exec vitest run
ui/src/components/Sidebar.test.tsx` passed: 1 file, 8 tests.
- CI and Greptile should rerun on the latest push.

## Risks

- Broad UI surface area: the experimental flag keeps the classic nav
default, but changed shared components such as `EntityRow`,
`ProjectTile`, and agent status badges could affect multiple pages.
- Database migration: `projects.icon` is additive and nullable, but
migration ordering and portability import/export must stay aligned.
- The Issue-to-Task copy migration is now separated into #7651, so
reviewers should evaluate this PR as IA/project/agent presentation work
only.
- Visual regressions are possible across smaller widths because the PR
intentionally changes dense list-row layouts.

## Model Used

Claude Opus 4.8 assisted the original feature commits.
Paperclip-Paperclip agents assisted some planning/design commits. Codex
/ GPT-5-class coding agent with shell, GitHub CLI, and repository access
performed this PR-readiness cleanup and split.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Dotta <bippadotta@protonmail.com>
This commit is contained in:
scotttong
2026-06-06 07:17:27 -07:00
committed by GitHub
parent 4d5322c821
commit eaef47f4c7
60 changed files with 21179 additions and 390 deletions
@@ -46,6 +46,7 @@ function makeProject(id: string, name: string): Project {
leadAgentId: null,
targetDate: null,
color: null,
icon: null,
env: null,
pauseReason: null,
pausedAt: null,
+1
View File
@@ -82,6 +82,7 @@ describe("issueDetailBreadcrumb", () => {
leadAgentId: null,
targetDate: null,
color: null,
icon: null,
env: null,
pauseReason: null,
pausedAt: null,
@@ -594,6 +594,7 @@ describe("optimistic issue comments", () => {
leadAgentId: null,
targetDate: null,
color: null,
icon: null,
env: null,
pauseReason: null,
pausedAt: null,
+16
View File
@@ -0,0 +1,16 @@
import { describe, expect, it } from "vitest";
import { formatProjectBudget } from "./utils";
describe("formatProjectBudget", () => {
it("renders a /mo suffix for monthly budgets", () => {
expect(formatProjectBudget({ amountCents: 120_000, windowKind: "calendar_month_utc" })).toBe("$1,200.00/mo");
});
it("renders the bare amount for lifetime budgets", () => {
expect(formatProjectBudget({ amountCents: 50_000, windowKind: "lifetime" })).toBe("$500.00");
});
it("formats sub-dollar amounts with cents", () => {
expect(formatProjectBudget({ amountCents: 150, windowKind: "lifetime" })).toBe("$1.50");
});
});
+94
View File
@@ -0,0 +1,94 @@
import {
Atom,
Box,
Boxes,
Brain,
Briefcase,
Bug,
Code,
Cog,
Compass,
Cpu,
Crown,
Database,
FileCode,
Flame,
Folder,
Gem,
GitBranch,
Globe,
Hammer,
Heart,
Hexagon,
Layers,
Lightbulb,
Lock,
Mail,
MessageSquare,
Package,
Puzzle,
Radar,
Rocket,
Search,
Shield,
Sparkles,
Star,
Target,
Telescope,
Terminal,
Wrench,
Zap,
type LucideIcon,
} from "lucide-react";
import { PROJECT_ICON_NAMES, type ProjectIconName } from "@paperclipai/shared";
export const PROJECT_ICONS: Record<ProjectIconName, LucideIcon> = {
folder: Folder,
rocket: Rocket,
code: Code,
terminal: Terminal,
database: Database,
globe: Globe,
package: Package,
boxes: Boxes,
box: Box,
layers: Layers,
briefcase: Briefcase,
compass: Compass,
target: Target,
flame: Flame,
zap: Zap,
star: Star,
bug: Bug,
wrench: Wrench,
hammer: Hammer,
lightbulb: Lightbulb,
sparkles: Sparkles,
shield: Shield,
lock: Lock,
search: Search,
cog: Cog,
brain: Brain,
cpu: Cpu,
"git-branch": GitBranch,
"file-code": FileCode,
puzzle: Puzzle,
gem: Gem,
atom: Atom,
heart: Heart,
mail: Mail,
"message-square": MessageSquare,
crown: Crown,
radar: Radar,
telescope: Telescope,
hexagon: Hexagon,
};
const DEFAULT_ICON: ProjectIconName = "folder";
export function getProjectIcon(iconName: string | null | undefined): LucideIcon {
if (iconName && PROJECT_ICON_NAMES.includes(iconName as ProjectIconName)) {
return PROJECT_ICONS[iconName as ProjectIconName];
}
return PROJECT_ICONS[DEFAULT_ICON];
}
+40 -10
View File
@@ -83,20 +83,50 @@ export const statusBadge: Record<string, string> = {
export const statusBadgeDefault = "bg-muted text-muted-foreground";
// ---------------------------------------------------------------------------
// Agent status dot — solid background for small indicator dots
// Agent status — brand state system (PAP-75)
//
// The agents section shows four states only. Each maps to a brand colour name;
// the indicator is a brand heartbeat capsule (8×16, r4) and the row chip is a
// brand `.task-chip` (1px border, light/dark variants). `active` is dead code
// that folds into idle; `running` pulses and `error` blinks. `terminated` and
// `pending_approval` are excluded from the list but may still render on detail
// pages, where they fall through to the gray default.
// ---------------------------------------------------------------------------
export const agentStatusDot: Record<string, string> = {
running: "bg-cyan-400 animate-pulse",
active: "bg-green-400",
paused: "bg-yellow-400",
idle: "bg-yellow-400",
pending_approval: "bg-amber-400",
error: "bg-red-400",
archived: "bg-neutral-400",
export type AgentBadgeColor = "gray" | "blue" | "amber" | "red";
/** Agent status → brand colour name. `active` aliases idle (never assigned). */
export const agentStatusColor: Record<string, AgentBadgeColor> = {
idle: "gray",
active: "gray",
running: "blue",
paused: "amber",
error: "red",
};
export const agentStatusDotDefault = "bg-neutral-400";
export const agentStatusColorDefault: AgentBadgeColor = "gray";
/** Brand `.task-chip` styles (1px border) per colour name — light + dark. */
export const agentStatusBadge: Record<AgentBadgeColor, string> = {
gray: "bg-[#F5F3F0] text-[#52585D] border-[#A8AEB2] dark:bg-[#6e696024] dark:text-[#9A958A] dark:border-[#9e958a73]",
blue: "bg-[#DBEAFE] text-[#1D4ED8] border-[#2563EB] dark:bg-[#2563eb2e] dark:text-[#2563EB] dark:border-[#2563eb73]",
amber: "bg-[#FEF3C7] text-[#B45309] border-[#F59E0B] dark:bg-[#f59e0b24] dark:text-[#F59E0B] dark:border-[#f59e0b73]",
red: "bg-[#FEE2E2] text-[#991B1B] border-[#DC2626] dark:bg-[#dc26262e] dark:text-[#DC2626] dark:border-[#dc262673]",
};
/** Heartbeat-capsule fill (solid) per colour name. gray darkens in dark mode. */
export const agentStatusCapsule: Record<AgentBadgeColor, string> = {
gray: "bg-[#A8AEB2] dark:bg-[#6E6960]",
blue: "bg-[#2563EB]",
amber: "bg-[#F59E0B]",
red: "bg-[#DC2626]",
};
/** Per-status capsule motion (running pulses, error blinks). Honors reduced-motion. */
export const agentStatusMotion: Record<string, string> = {
running: "hb-pulse",
error: "hb-blink",
};
// ---------------------------------------------------------------------------
// Priority colors
+9
View File
@@ -29,6 +29,15 @@ export function formatNumber(n: number): string {
return n.toLocaleString("en-US");
}
/**
* Format a project's budget for the projects list view (IA Phase 4 PAP-60).
* Monthly budgets render a `/mo` suffix; lifetime budgets show the bare amount.
*/
export function formatProjectBudget(budget: { amountCents: number; windowKind: string }): string {
const amount = formatCents(budget.amountCents);
return budget.windowKind === "calendar_month_utc" ? `${amount}/mo` : amount;
}
export function formatDate(date: Date | string): string {
return new Date(date).toLocaleDateString("en-US", {
month: "short",